Kotlin-Generics

Generics, d. h. "parametrisierte Typen", parametrisieren Typen und können für Klassen, Schnittstellen und Methoden verwendet werden.

Wie Java bietet auch Kotlin Generics, um Typsicherheit zu gewährleisten und den Aufwand für Typumwandlungen zu vermeiden.

Deklarieren Sie eine generische Klasse:

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

Beim Erstellen einer Instanz der Klasse müssen wir den Typparameter angeben:

val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。

Das folgende Beispiel übergibt ganzzahlige Daten und eine Zeichenkette an die generische Klasse Box:

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

fun main(args: Array<String>) {
    var boxInt = Box<Int>(10)
    var boxString = Box<String>("Example")

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

Das Ausgabeergebnis ist:

10
Example

Beim Definieren einer generischen Typvariablen können Sie den Typparameter vollständig ausschreiben oder ihn weglassen, wenn der Compiler den Typparameter automatisch ableiten kann.

Die Deklaration generischer Funktionen in Kotlin ist dieselbe wie in Java; der Typparameter sollte vor dem Funktionsnamen stehen:

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

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

Beim Aufruf einer generischen Funktion kann der generische Parameter weggelassen werden, wenn der Typparameter abgeleitet werden kann.

Das folgende Beispiel erstellt eine generische Funktion doPrintln, die unterschiedliche übergebene Typen entsprechend verarbeitet:

fun main(args: Array<String>) {
    val age = 23
    val name = "example"
    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 不是整型,也不是字符串")
    }
}

Das Ausgabeergebnis ist:

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

Generische Einschränkungen

Wir können generische Einschränkungen verwenden, um die erlaubten Typen für einen bestimmten Parameter festzulegen.

In Kotlin wird : verwendet, um die obere Grenze eines generischen Typs einzuschränken.

Die häufigste Einschränkung ist die obere Grenze:

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

ComparableUntertypen des gebundenen Typs können T ersetzen. Zum Beispiel:

sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型

Die standardmäßige obere Grenze ist Any?.

Für mehrere Obergrenzen-Einschränkungen kann die where-Klausel verwendet werden:

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

Varianz

Kotlin hat keine Wildcard-Typen; es hat zwei andere Dinge: Deklarationsstellen-Varianz und Typprojektionen.

Deklarationsstellen-Varianz

Die Deklarationsstellen-Varianz verwendet kovariante Annotationsmodifikatoren: in und out. Konsument in, Produzent out.

Die Verwendung von out macht einen Typparameter kovariant. Ein kovarianter Typparameter kann nur als Ausgabe verwendet werden und kann als Rückgabewerttyp, aber nicht als Eingabeparameter-Typ verwendet werden:

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

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

Die Verwendung von in macht einen Typparameter kontravariant. Ein kontravarianter Typparameter kann nur als Eingabe verwendet werden und kann als Eingabeparameter-Typ, aber nicht als Rückgabewerttyp verwendet werden:

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

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

Sternprojektion

Manchmal möchten Sie angeben, dass Sie keine Informationen über den Typparameter kennen, ihn aber trotzdem sicher verwenden möchten. Die sogenannte "sichere Verwendung" bedeutet hier, eine Typprojektion für einen generischen Typ zu definieren, wobei verlangt wird, dass alle konkreten Instanzen dieses generischen Typs Untertypen dieser Projektion sind.

Für dieses Problem bietet Kotlin eine Syntax namens Sternprojektion:

  • Wenn der Typ als Foo<out T> definiert ist, wobei T ein kovarianter Typparameter mit Obergrenze TUpper ist, ist Foo<> äquivalent zu Foo<out TUpper>. Das bedeutet, dass Sie, wenn T unbekannt ist, sicher einen Wert vom Typ TUpper aus Foo< lesen können>.
  • Wenn der Typ als Foo<in T> definiert ist, wobei T ein kontravarianter Typparameter ist, ist Foo<> äquivalent zu Foo<in Nothing>. Das bedeutet, dass Sie, wenn T unbekannt ist, nichts sicher in Foo<> schreiben können.
  • Wenn der Typ als Foo<T> definiert ist, wobei T ein kovarianter Typparameter mit Obergrenze TUpper ist, ist Foo<*> zum Lesen von Werten äquivalent zu Foo<out TUpper>; zum Schreiben von Werten ist es äquivalent zu Foo<in Nothing>.

Wenn ein generischer Typ mehrere Typparameter hat, kann jeder Typparameter unabhängig projiziert werden. Zum Beispiel, wenn der Typ als interface Function<in T, out U> definiert ist, können die folgenden Sternprojektionen auftreten:

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

Hinweis: Die Sternprojektion ähnelt den Raw-Typen von Java sehr, kann aber sicher verwendet werden.

Andere Erweiterungen