In this tutorial, we will dive into the world of Kotlin generics. The goal is to understand how generics can make our code more flexible and type-safe, reducing runtime errors and increasing reusability.
You will learn:
- What are generics in Kotlin
- How to implement generics in classes, interfaces, and functions
- Understanding variance in Kotlin generics
Prerequisites:
- Basic understanding of Kotlin syntax and classes
- Familiarity with programming concepts like data types and functions
Generics are a powerful feature that allows you to write classes and methods that can be used with different types while maintaining type safety. This means that you could write a single method or class that could work with different types, and the Kotlin compiler would ensure that you are using the types correctly.
class Box<T>(t: T) {
var value = t
}
Here T
is a type parameter representing any type. You can use Box
with any type:
val box1: Box<Int> = Box(1)
val box2: Box<String> = Box("Hello")
Box
for any type:fun <T> boxOf(t: T): Box<T> = Box(t)
You can call this function with any type:
val box1 = boxOf(1)
val box2 = boxOf("Hello")
Box<Parent>
wherever a Box<Child>
is required and vice versa. Kotlin has two keywords to express variance:out
: makes a type parameter covariant. It means you can use a Box<Child>
wherever a Box<Parent>
is required.in
: makes a type parameter contravariant. It means you can use a Box<Parent>
wherever a Box<Child>
is required.Here's a generic class Box
that can hold any type of value:
class Box<T>(t: T) {
var value = t
}
fun main() {
val box1: Box<Int> = Box(1)
println(box1.value) // Outputs: 1
val box2: Box<String> = Box("Hello")
println(box2.value) // Outputs: "Hello"
}
In this example, T
is a type parameter that represents any type.
Here's a generic function that can create a Box
for any type:
class Box<T>(t: T) {
var value = t
}
fun <T> boxOf(t: T): Box<T> = Box(t)
fun main() {
val box1 = boxOf(1)
println(box1.value) // Outputs: 1
val box2 = boxOf("Hello")
println(box2.value) // Outputs: "Hello"
}
In this example, boxOf
is a function with a type parameter T
. It can create a Box
for any type.
In this tutorial, we learned about generics in Kotlin, including how to implement them in classes, interfaces, and functions. We also explored the concept of variance for advanced usage of generics.
For further learning, consider exploring topics like upper bounds in generics, generic constraints, and type projections in Kotlin.
Exercise 1: Implement a generic function swap
that swaps the elements of a pair.
Hint: You can use Pair<T, T>
to represent a pair of elements.
Solution:
kotlin
fun <T> swap(pair: Pair<T, T>): Pair<T, T> = Pair(pair.second, pair.first)
This function uses the type parameter T
to work with pairs of any type.
Exercise 2: Implement a generic class Stack
with methods push
, pop
, and isEmpty
.
Solution:
```kotlin
class Stack
private val elements = mutableListOf
fun push(item: T) {
elements.add(item)
}
fun pop(): T? {
if (isEmpty()) {
return null
}
return elements.removeAt(elements.size - 1)
}
fun isEmpty() = elements.isEmpty()
}
``
This class uses the type parameter
T` to work with stacks of any type.
For more practice, try implementing other data structures like queues or binary trees using generics.