Kotlin Generics
Generics, also known as "parameterized types," parameterize types and can be used in classes, interfaces, and methods.
Like Java, Kotlin also provides generics to ensure type safety and eliminate the hassle of type casting.
Declare a generic class:
class Box<T>(t: T) {
var value = t
}
When creating an instance of the class, we need to specify the type argument:
val box: Box<Int> = Box<Int>(1)
// or
val box = Box(1) // The compiler will infer the type, 1 is of type Int, so the compiler knows we mean Box<Int>.
The following example passes integer data and a string to the generic class Box:
class Box<T>(t: T) {
var value = t
}
fun main(args: Array<String>) {
var boxInt = Box<Int>(10)
var boxString = Box<String>("tutorialpro")
println(boxInt.value)
println(boxString.value)
}
The output will be:
10
tutorialpro
Define a generic type variable, you can fully specify the type argument, and if the compiler can automatically determine the type argument, you can also omit the type argument.
Kotlin generic function declaration is the same as Java, and the type argument should be placed in front of the function name:
fun <T> boxIn(value: T) = Box(value)
// The following are all legal statements
val box4 = boxIn<Int>(1)
val box5 = boxIn(1) // The compiler will infer the type
When calling a generic function, if the type argument can be inferred, the generic argument can be omitted.
The following example creates a generic function doPrintln, which handles different types differently according to what is passed in:
fun main(args: Array<String>) {
val age = 23
val name = "tutorialpro"
val bool = true
doPrintln(age) // Integer
doPrintln(name) // String
doPrintln(bool) // Boolean
}
fun <T> doPrintln(content: T) {
when (content) {
is Int -> println("The integer is $content")
is String -> println("String in uppercase: ${content.toUpperCase()}")
else -> println("T is neither an integer nor a string")
}
}
The output will be:
The integer is 23
String in uppercase: TUTORIALPRO
T is neither an integer nor a string
Generic Constraints
We can use generic constraints to set which types a given parameter is allowed to use.
In Kotlin, the colon (:) is used to constrain the upper bound of a generic type.
The most common constraint is the upper bound:
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
Comparable
sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
The default upper bound is Any?.
For multiple upper bound constraints, a where clause can be used:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
Variance
Kotlin does not have wildcard types; it has two other things: declaration-site variance and type projections.
Declaration-site Variance
Declaration-site variance uses covariant and contravariant annotations: in for consumers, out for producers.
Using out makes a type parameter covariant; covariant type parameters can only be used as outputs, can be used as return types but cannot be used as argument types:
// Define a class that supports covariance
class tutorialpro<out A>(val a: A) {
fun foo(): A {
return a
}
}
fun main(args: Array<String>) {
var strCo: tutorialpro<String> = tutorialpro("a")
var anyCo: tutorialpro<Any> = tutorialpro<Any>("b")
anyCo = strCo
println(anyCo.foo()) // Outputs a
}
Using in makes a type parameter contravariant; contravariant type parameters can only be used as inputs, can be used as argument types but cannot be used as return types:
``` // Define a class that supports contravariance class tutorialpro<in A>(a: A) { fun foo(a: A) { } }