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

Generics en Kotlin

La genericidad, es decir, "tipos parametrizados", parametriza los tipos, que se pueden usar en clases, interfaces y métodos.

Como Java, Kotlin también proporciona genericidad, que asegura la seguridad de tipo y elimina la molestia de la conversión de tipo.

Se declara una clase genérica:

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

Al crear ejemplos de clase, debemos especificar los parámetros de tipo:

val box: Box<Int> = Box<Int>(1)
// o
val box = Box(1) // El compilador realiza la inferencia de tipo,1 El tipo es Int, por lo que el compilador sabe que decimos Box<Int>.

El siguiente ejemplo transmite datos de tipo entero y 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>("w3codebox")
    println(boxInt.value)
    println(boxString.value)
}

El resultado de la salida es:

10
w3codebox

Se puede definir una variable de tipo genérico, se puede escribir completamente el parámetro de tipo si el compilador puede determinar automáticamente el parámetro de tipo, también se puede omitir el parámetro de tipo.

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

fun <T> boxIn(value: T) = Box(value)
// Todos estos son enunciados válidos
val box4 = boxIn<Int>(1)
val box5 = boxIn(1)     // El compilador realiza la inferencia de tipo

Al llamar a una función genérica, si se puede inferir el parámetro de tipo, se puede omitir el parámetro de tipo genérico.

El siguiente ejemplo crea una función genérica doPrintln, que realiza el procesamiento correspondiente según el tipo de entrada transmitido:

fun main(args: Array<String>) {
    val age = 23
    val name = "w3codebox"
    val bool = true
    doPrintln(age)    // Entero
    doPrintln(name)   // Cadena
    doPrintln(bool)   // Boolean
}
fun <T> doPrintln(content: T) {
    when (content) {}}
        is Int -> println("El número entero es $content")
        is String -> println("Convertir una cadena a mayúsculas: ${content.toUpperCase()}")
        else -> println("T no es un entero ni una cadena")
    }
}

El resultado de la salida es:

El número entero es 23
Convertir una cadena a mayúsculas: w3codebox
T no es un entero ni una cadena

Restricciones genéricas

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

Kotlin usa : para restricciones de tipo superior de generics.

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

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

Los subtipos de Comparable pueden reemplazar a T. Por ejemplo:

sort(listOf(1, 2, 3)) // OK. Int es un subtipo de Comparable<Int>
sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> no es un subtipo de 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>
    donde T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

variación

Kotlin no tiene tipos de wildcard, tiene dos otras cosas: variación de declaración (declaration-y las proyecciones de tipo (type projections).

Declaración de variación

El uso de covarianza en la declaración se indica con el modificador de anotación de covarianza: in, out, consumidor in, productor out.

Usar out para hacer que un parámetro de tipo sea covariante, los parámetros de tipo covariantes solo pueden usarse como tipos de salida, pueden ser tipos de retorno pero no tipos de entrada:

// definir una clase que admite la covarianza
class w3codebox<out A>(val a: A) {
    fun foo(): A {
        devolver a
    }
}
fun main(args: Array<String>) {
    var strCo: w3codebox<String> = w3codebox("a")
    var anyCo: w3codebox<Any> = w3codebox<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // Salida a
}

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 entrada pero no pueden ser tipos de salida:

// Definir una clase que soporte la covarianza inversa
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
}

Proyección de asterisco

A veces, podrías querer expresar que no tienes información alguna sobre los parámetros de tipo, pero aún así希望能够 usarlos de manera segura. Aquí, "usar de manera segura" significa definir una proyección de tipo para un tipo genérico, que requiere que todas las instancias de los entes del tipo genérico sean subtipos de esta proyección.

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

  • Supongamos que el tipo se define como Foo<out T>, donde T es un parámetro de tipo covariante, el límite superior (upper bound) es TUpper, Foo<> es equivalente a Foo<out TUpper>. Esto indica que cuando T es desconocido, puedes usarlo de manera segura de Foo<> lee valores del tipo TUpper.

  • Supongamos que el tipo se define como Foo<in T>, donde T es un parámetro de tipo contravariante, Foo<> es equivalente a Foo<inNothing>. Esto indica que cuando T es desconocido, no puedes usarlo de manera segura en Foo<> escribe cualquier cosa.

  • Supongamos que el tipo se define como Foo<T>, donde T es un parámetro de tipo covariante, el límite superior (upper bound) es TUpper, para el caso de lectura de valores, Foo<*es equivalente a Foo<out TUpper>, para el caso de escritura de valores, es equivalente a Foo<in Nothing>.

Si un tipo genérico contiene múltiples parámetros de tipo, cada parámetro de tipo puede proyectarse de manera individual. Por ejemplo, si el tipo se define como interface Function<in T, out U>, se pueden presentar los siguientes tipos de proyección de asterisco:

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

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

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

Atención: La propagación de asteriscos es muy similar a los tipos nativos (raw type) de Java, pero se puede usar de manera segura