English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Kotlin Generics

Generics, i.e., "parameterized types", parameterize the type, which can be used in classes, interfaces, and methods.

Like Java, Kotlin also provides generics to ensure type safety and eliminate the trouble of type casting.

Declare a generic class:

class Box<T>(t: T) {
    var value = t

When creating an example of a class, we need to specify the type parameter:

val box: Box<Int> = Box<Int>(1)
// or
val box = Box(1) // The compiler will perform type inference,1 Type Int, so the compiler knows we are talking about Box<Int>.

The following example passes integer data and strings 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>("w3codebox")
    println(boxInt.value)
    println(boxString.value)

The output result is:

10
w3codebox

Define generic type variables, you can fully specify the type parameters, and if the compiler can automatically determine the type parameters, you can also omit the type parameters.

The declaration of Kotlin generic functions is the same as Java, and the type parameters should be placed before the function name:

fun <T> boxIn(value: T) = Box(value)
// The following are all valid statements
val box4 = boxIn<Int>(1)
val box5 = boxIn(1)     // The compiler will perform type inference

When calling a generic function, if the type parameter can be inferred, the generic parameter can be omitted.

The following example creates a generic function doPrintln, which performs corresponding processing based on the different types passed in:

fun main(args: Array<String>) {
    val age = 23
    val name = "w3codebox"
    val bool = true
    doPrintln(age)    // Integer type
    doPrintln(name)   // String
    doPrintln(bool)   // Boolean type

fun <T> doPrintln(content: T) {
    when (content) {}}
        is Int -> println("Integer number is $content")
        is String -> println("Convert string to uppercase: ${content.toUpperCase()}")
        else -> println("T is not an integer nor a string")
    

The output result is:

Integer number is 23
Convert string to uppercase: w3codebox
T is not an integer nor a string

Generic constraints

We can use generic constraints to set the types that a given parameter is allowed to use.

Kotlin uses : to constrain the type upper bound of generics.

The most common constraint is the upper bound (upper bound):

fun <T : Comparable<T>> sort(list: List<T>) {
    // ……

Subtypes of Comparable can replace T. For example:

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, you can use the where clause:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }

Covariance

Kotlin does not have wildcard types; it has two other things: declaration site variance (declaration-site variance) and type projections (type projections).

Declaration site variance

Use covariance annotation modifiers: in, out, consumer in, producer out at the declaration site.

Use out to make a type parameter covariant; covariant type parameters can only be used as output and can be used as return types but cannot be used as input types:

// Define a covariant class
class w3codebox<out A>(val a: A) {
    fun foo(): A {
        return a
    

fun main(args: Array<String>) {
    var strCo: w3codebox<String> = w3codebox("a")
    var anyCo: w3codebox<Any> = w3codebox<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // Output a

in makes a type parameter contravariant; contravariant type parameters can only be used as input, and cannot be used as return types:

// Define a class that supports covariance
class w3codebox<in A>(a: A) {
    fun foo(a: A) {
    

fun main(args: Array<String>) {
    var strDCo = w3codebox("a")
    var anyDCo = w3codebox<Any>("b")
    strDCo = anyDCo

Star projection

Sometimes, you may want to express that you do not know any information about the type parameter, but still want to use it safely. By 'safely using', it refers to defining a type projection for a generic type, which requires all instances of the generic type to be a subtype of this projection.

For this issue, Kotlin provides a syntax called star projection (star-projection):

  • If the type is defined as Foo<out T>, where T is a covariant type parameter with an upper bound of TUpper, Foo<Is equivalent to Foo<out TUpper>. It indicates that when T is unknown, you can safely extract from Foo<Reads a value of type TUpper.

  • If the type is defined as Foo<in T>, where T is a contravariant type parameter, Foo<Is equivalent to Foo<inNothing>. It indicates that when T is unknown, you cannot safely pass to Foo<Writes anything.

  • If the type is defined as Foo<T>, where T is a covariant type parameter with an upper bound of TUpper, for reading values, Foo<*Is equivalent to Foo<out TUpper>, and for writing values, it is equivalent to Foo<in Nothing>.

If a generic type has multiple type parameters, each type parameter can be projected individually. For example, if the type is defined as interface Function<in T, out U>, the following star projections can appear:

  • Function<*, String> , represents Function<in Nothing, String> ;

  • Function<Int, *> , represents Function<Int, out Any?> ;

  • Function<, > , represents Function<in Nothing, out Any?> .

Note: Asterisk projection is very similar to Java's native types (raw type), but it can be used safely