Kotlin learning: Kotlin generics

Kotlin generics

Generics, namely "parameterized types", parameterize types and can be used in classes, interfaces, and methods.

Like Java, Kotlin also provides generics, which guarantees type safety and eliminates the trouble of forced conversion.

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 parameter

val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 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>("Runoob")

    println(boxInt.value)
    println(boxString.value)
}

The output result is

10
Runoob

To define a generic type variable, you can completely specify the type parameters. If the compiler can automatically infer the type parameters, you can also omit the type parameters.

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

fun <T> boxIn(value: T) = Box(value)

// 以下都是合法语句
val box4 = boxIn<Int>(1)
val box5 = boxIn(1)     // 编译器会进行类型推断

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, and the function does the corresponding processing according to the different types passed in

fun main(args: Array<String>) {
    val age = 23
    val name = "runoob"
    val bool = true

    doPrintln(age)    // 整型
    doPrintln(name)   // 字符串
    doPrintln(bool)   // 布尔型
}

fun <T> doPrintln(content: T) {

    when (content) {
        is Int -> println("整型数字为 $content")
        is String -> println("字符串转换为大写:${content.toUpperCase()}")
        else -> println("T 不是整型,也不是字符串")
    }
}

The output result is

整型数字为 23
字符串转换为大写:RUNOOB
T 不是整型,也不是字符串

Generic constraints

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

Used in Kotlin: Constrain the upper limit of generic types.

The most common constraint is the upper bound

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

Subtypes of Comparable can replace T. E.g

sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 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() }
}

Type change

There is no wildcard type in Kotlin, it has two other things: declaration-site variance and type projections

Declaration

The type mutation at the declaration uses covariant annotation modifiers: in, out, consumer in, producer out.

Use out to make a type parameter covariant. Covariant type parameters can only be used as output. They can be used as return value types but cannot be used as input parameters.

// 定义一个支持协变的类
class Runoob<out A>(val a: A) {
    fun foo(): A {
        return a
    }
}

fun main(args: Array<String>) {
    var strCo: Runoob<String> = Runoob("a")
    var anyCo: Runoob<Any> = Runoob<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // 输出 a
}

in makes a type parameter contravariant, contravariant type parameters can only be used as input, can be used as the type of the input parameter but cannot be used as the type of the return value

// 定义一个支持逆变的类
class Runoob<in A>(a: A) {
    fun foo(a: A) {
    }
}

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

Star projection

Sometimes, you may want to indicate that you do not know any information about the type parameter, but still want to be able to use it safely. The so-called "safe use" here means that a type projection is defined for a generic type, and the generic type is required All entity instances of are subtypes of this projection.

For this problem, Kotlin provides a syntax called star-projection:

  • If the type is defined as Foo<out T>, where T is a covariant type parameter, the upper bound is Tupper, and Foo< > is equivalent to Foo<out TUpper>. It means that when T is unknown, you It is safe to read a value of type Tupper from Foo< >.
  • If the type is defined as Foo<in T>, where T is a reverse covariant type parameter, Foo< > is equivalent to Foo<inNothing>. It means that when T is unknown, you cannot safely write to Foo< > Into anything.
  • If the type is defined as Foo<T>, where T is a covariant type parameter, and the upper bound is Tupper, for reading the value, Foo<*> is equivalent to Foo<out TUpper>, for writing When entering a value, it is equivalent to Foo<in Nothing>.

If there are multiple type parameters in a generic type, then each type parameter can be cast separately. For example, if the type is defined as interface Function<in T, out U>, then the following types of asterisk projections can appear:

  1. Function<*, String> , 代表 Function<in Nothing, String> ;
  2. Function<Int, *> , 代表 Function<Int, out Any?> ;
  3. Function<> , 代表 Function<in Nothing, out Any?> .

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

Guess you like

Origin blog.csdn.net/PrisonJoker/article/details/113926226