Génériques Kotlin

Les génériques, c'est-à-dire les « types paramétrés », paramètrent les types et peuvent être utilisés sur les classes, les interfaces et les méthodes.

Comme Java, Kotlin fournit également des génériques pour garantir la sûreté du typage et éliminer les tracas du transtypage.

Déclarer une classe générique :

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

Lors de la création d'une instance de la classe, nous devons spécifier le paramètre de type :

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

L'exemple suivant passe des données de type entier et une chaîne à la classe générique 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)
}

Le résultat de sortie est:

10
Example

Lors de la définition d'une variable de type générique, vous pouvez écrire explicitement le paramètre de type, ou l'omettre si le compilateur peut déduire automatiquement le paramètre de type.

La déclaration des fonctions génériques de Kotlin est la même que celle de Java ; le paramètre de type doit être placé avant le nom de la fonction :

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

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

Lors de l'appel d'une fonction générique, si le paramètre de type peut être déduit, le paramètre générique peut être omis.

L'exemple suivant crée une fonction générique doPrintln, qui traite en conséquence les différents types qui lui sont transmis :

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 不是整型,也不是字符串")
    }
}

Le résultat de sortie est:

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

Contraintes génériques

Nous pouvons utiliser des contraintes génériques pour définir les types autorisés pour un paramètre donné.

En Kotlin, : est utilisé pour contraindre la borne supérieure d'un type générique.

La contrainte la plus courante est la borne supérieure :

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

ComparableLes sous-types du type lié peuvent remplacer T. Par exemple :

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

La borne supérieure par défaut est Any?.

Pour plusieurs contraintes de borne supérieure, la clause where peut être utilisée :

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 n'a pas de types jokers ; il possède deux autres notions : la variance au site de déclaration et les projections de type.

Variance au site de déclaration

La variance au site de déclaration utilise des modificateurs d'annotation de covariance : in et out. Consommateur in, producteur out.

Utiliser out rend un paramètre de type covariant. Un paramètre de type covariant ne peut être utilisé qu'en sortie, et peut être utilisé comme type de valeur de retour mais pas comme type de paramètre d'entrée :

// 定义一个支持协变的类
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
}

Utiliser in rend un paramètre de type contravariant. Un paramètre de type contravariant ne peut être utilisé qu'en entrée, et peut être utilisé comme type de paramètre d'entrée mais pas comme type de valeur de retour :

// 定义一个支持逆变的类
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
}

Projection étoile

Parfois, vous pouvez vouloir indiquer que vous ne connaissez aucune information sur le paramètre de type, mais tout de même l'utiliser en toute sécurité. L'utilisation dite « sûre » ici signifie définir une projection de type pour un type générique, exigeant que toutes les instances concrètes de ce type générique soient des sous-types de cette projection.

Pour ce problème, Kotlin fournit une syntaxe appelée star-projection :

  • Si le type est défini comme Foo<out T>, où T est un paramètre de type covariant avec pour borne supérieure TUpper, Foo<> est équivalent à Foo<out TUpper>. Cela signifie que lorsque T est inconnu, vous pouvez lire en toute sécurité une valeur de type TUpper depuis Foo<>.
  • Si le type est défini comme Foo<in T>, où T est un paramètre de type contravariant, Foo<> est équivalent à Foo<in Nothing>. Cela signifie que lorsque T est inconnu, vous ne pouvez pas écrire en toute sécurité quoi que ce soit dans Foo<>.
  • Si le type est défini comme Foo<T>, où T est un paramètre de type covariant avec pour borne supérieure TUpper, pour la lecture des valeurs, Foo<*> est équivalent à Foo<out TUpper> ; pour l’écriture des valeurs, il est équivalent à Foo<in Nothing>.

Si un type générique a plusieurs paramètres de type, chaque paramètre de type peut être projeté indépendamment. Par exemple, si le type est défini comme interface Function<in T, out U>, les projections d'étoile suivantes peuvent apparaître :

  1. Function<*, String>, représentant Function<in Nothing, String> ;
  2. Function<Int, *>, représentant Function<Int, out Any?> ;
  3. Function<, >, représentant Function<in Nothing, out Any?>.

Remarque : La projection d'étoile est très similaire aux types bruts de Java, mais elle peut être utilisée en toute sécurité.

Autres extensions