Genéricos de Kotlin

Los genéricos, es decir, "tipos parametrizados", parametrizan tipos y pueden usarse en clases, interfaces y métodos.

Al igual que Java, Kotlin también proporciona genéricos, lo que garantiza la seguridad de tipos y elimina la molestia de las conversiones forzadas de tipos.

Declarar una clase genérica:

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

Al crear una instancia de la clase, necesitamos especificar los parámetros de tipo:

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

El siguiente ejemplo pasa datos de tipo entero y una cadena a la clase genérica Box:

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

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

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

El resultado es:

10
Ejemplo

Al definir variables de tipos genéricos, se pueden escribir los parámetros de tipo completos; si el compilador puede inferir automáticamente los parámetros de tipo, también se pueden omitir.

La declaración de funciones genéricas en Kotlin es igual que en Java: los parámetros de tipo deben colocarse antes del nombre de la función:

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

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

Al llamar a una función genérica, si se pueden inferir los parámetros de tipo, se pueden omitir los parámetros genéricos.

El siguiente ejemplo crea la función genérica doPrintln, que realiza el procesamiento correspondiente según el tipo pasado:

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

El resultado es:

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

Restricciones de los genéricos

Podemos usar restricciones de genéricos para establecer los tipos permitidos para un parámetro dado.

En Kotlin, se usa ":" para restringir el límite superior de tipo de los genéricos.

La restricción más común es el límite superior (upper bound):

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

ComparableLos subtipos pueden sustituir a T. Por ejemplo:

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

El límite superior predeterminado es Any?.

Para múltiples condiciones de límite superior, se puede usar la cláusula where:

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

Varianza

Kotlin no tiene tipos comodín; tiene otras dos cosas: varianza en la declaración (declaration-site variance) y proyección de tipos (type projections).

Varianza en la declaración

La varianza en la declaración utiliza los modificadores de anotación de covarianza: in, out; consumidor in, productor out.

Usar out hace que un parámetro de tipo sea covariante. Los parámetros de tipo covariantes solo pueden usarse como salida; pueden ser tipos de retorno pero no tipos de entrada:

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

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

Usar in hace que un parámetro de tipo sea contravariante. Los parámetros de tipo contravariantes solo pueden usarse como entrada; pueden ser tipos de parámetros de entrada pero no tipos de retorno:

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

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

Proyección de estrella

A veces, es posible que quieras indicar que no conoces ninguna información sobre los parámetros de tipo, pero aún así deseas poder usarlo de manera segura. Aquí, "usarlo de manera segura" significa definir una proyección de tipo para el tipo genérico, exigiendo que todas las instancias concretas de este tipo genérico sean subtipos de esta proyección.

Para este problema, Kotlin proporciona una sintaxis llamada proyección de estrella (star-projection):

  • Si el tipo se define como Foo<out T>, donde T es un parámetro de tipo covariante, con límite superior (upper bound) TUpper, Foo<> es equivalente a Foo<out TUpper>. Significa que, cuando T se desconoce, puedes leer con seguridad valores de tipo TUpper desde Foo<>.
  • Si el tipo se define como Foo<in T>, donde T es un parámetro de tipo contravariante, Foo<> es equivalente a Foo<inNothing>. Significa que, cuando T se desconoce, no puedes escribir con seguridad en Foo<> cualquier cosa.
  • Si el tipo se define como Foo<T>, donde T es un parámetro de tipo covariante, con límite superior (upper bound) TUpper, para lectura de valores, Foo<*> equivale a Foo<out TUpper>; para escritura de valores, equivale a Foo<in Nothing>.

Si un tipo genérico tiene múltiples parámetros de tipo, cada parámetro de tipo puede proyectarse de forma independiente. Por ejemplo, si el tipo se define como interface Function<in T, out U>, entonces pueden aparecer las siguientes proyecciones de estrella:

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

Nota: la proyección de estrella es muy similar al tipo primitivo (raw type) de Java, pero se puede usar de forma segura.

Otras extensiones