Genéricos en Kotlin. ¿Qué es? | Por Saravana Thiyagaraj | Maderaps | Septiembre de 2021
¿Qué es?
Estos son genéricos: List<String>
, List<Any>
, Map<Int, String>
, Jungle<T>
, y muchos más…
que es
T
existeJungle<T>
?
T
Es de cualquier tipo.Trátelo como un marcador de posición al definir Jungle<T>
igual que:
class Jungle<T>
fun obtainType(): T
// ...
Entonces, ¿cuál es la diferencia entre los dos
Jungle<T>
conJungle<Animal>
?
T
Es un marcador de posición y puede ser reemplazado por cualquier tipo en el proceso de implementación. Animal
Es uno de ellos. Es como:
fun main()
val jungle: Jungle<Animal> = Jungle()
Dicen «A diferencia de Array, ArrayList es inmutable». ¿Qué quiere decir esto?
Invariante Es una especie de variación, con Covarianza con Contravariante. P.ej, String
Sí subtipo Any
. pero ArrayList<String>
Subtipo que no es ArrayList<Any>
. Se llama Invariable.
si T
Es un subtipo U
, Luego:
- Invariante : si
Jungle<T>
Subtipo que no esJungle<U>
- Covarianza : si
Jungle<T>
Es un subtipoJungle<U>
- Inversor : si
Jungle<T>
Es un supertipoJungle<U>
Lo siguiente es imposible y provocará un error de compilación porque ArrayList
No cambia:
fun main()
val strings: ArrayList<String> = ArrayList()
val objects: ArrayList<Any> = strings // Compile error
Espere. ¿Por qué arroja un error de compilación? ¿Qué tiene de malo la conversión de tipos como esta?
Extendamos un poco el código y verá por qué no está permitido. Supongamos que el compilador no arrojará un error durante la conversión de tipos.Esto resultará en el siguiente error de tiempo de ejecución (porque lastItem
No es una cuerda, pero Int
):
fun main()
val strings: ArrayList<String> = ArrayList()
strings.add("One")
val objects: ArrayList<Any> = strings // Assuming no compile error
objects.add(5)
val lastItem: String = strings.last() // Runtime error
comprendido. Pero si este es el caso, entonces todos los tipos genéricos deberían ser Invariantes, ¿verdad? ¿Cómo es posible la covarianza y la contravarianza?
Puedes hacer un tipo Covarianza o Inversor Usando llamado Declarar las diferencias del sitioPero antes de eso, necesita comprender algunas cosas importantes.La razón por la que el ejemplo anterior arroja un «error de tiempo de ejecución» es que podemos agregar Int
. Si tu no tienes add
Método, entonces este problema no debería ocurrir.O más claramente, si no permite Escribir Operaciones sobre tipos, pero solo leer Operación, entonces podemos usarlo de manera segura como un tipo covariante.
Piénsalo.Si tienes uno ArrayList<String>
Pero no puede agregarle nada (sin operación de escritura), entonces puede asegurarse de que este ArrayList siempre contenga solo cadenas.Esto significa que no solo puede leer con seguridad String
A partir de él, también puede leerlo como Any
Ya que Any
Sí supertipo String
Esto solo es posible porque está impidiendo que la operación de escritura escriba T. Esta es la lógica detrás de declarar las diferencias de sitios.Entonces, para hacer nuestro Jungle<T>
Como covarianza, usamos out
como sigue:
class Jungle<out T>(val t: T)
fun obtainType(): T
return t
// fun insertType(t: T) // Not Possible (Type parameter T is declared as 'out', so no Write operation on type T)
fun main()
val jungleWithAnimals: Jungle<Animal> = Jungle(Cat())
val jungleWithAnything: Jungle<Any> = jungleWithAnimals // This won't be possible if you remove the `out`
Así que aquí básicamente le estamos diciendo al compilador Jungle
Solo permitido Read
funcionar T
mediante el uso out
Palabras clave.Ahora puedes escribir Jungle<Animal>
llegar Jungle<Any>
. esto no es nada Covarianza.
Del mismo modo, puede hacer Jungle
Inversor usar in
. Esto es exactamente lo contrario de lo que estamos hablando ahora, donde Escribir Está permitido, pero leer prohibido.
Vaya, eso tiene sentido.Acabo de comprobar el código fuente
List
En Kotlin, en realidad se define comoList<out E>
. asi queList
Es covariante, peroMutableList
No ha cambiado. Pero, ¿y si no tenemos una clase de comportamiento covariante?DigamosJungle<T>
Está definida como una biblioteca de terceros y queremos usarla como un tipo covariante, pero no podemos cambiar el código fuente. ¿Cómo es eso posible?
Entonces esto nos deja Usar diferencia de ubicación Llama Proyección de tipo. Tu (nosotros) podemos hacerlo Jungle<T>
Covarianza En el tipo T, al usarlo, se ve así:
fun main()
val jungleWithAnimals: Jungle<Animal> = Jungle(Cat())
val jungleWithAnything: Jungle<out Any> = jungleWithAnimals
// val type: Animal = jungleWithAnything.obtainType() // Not possible. You can only Read 'Any'
val type: Any = jungleWithAnything.obtainType()
// jungleWithAnything.insertType(Cat()) // Not possible. You can't Write.
Presta atención al uso out
aquí.Este para ti Covarianza Restringiéndote a escribir operaciones.Además, dado que el tipo aquí es Any
, Solo puedes leer como Any
En lugar de como Animal
. Como siempre, lo mismo se aplica a in
al igual que.Puede ayudarlo a usar tipos como Inversor.
Oh ya veo.Estaba navegando por un código y encontré algo similar
Jungle<T : Animal>
. ¿Qué significa?Creo que significaT
No solo el de cualquier tipo, sino que debe serAnimal
. ¿Esto es real?
Estás absolutamente en lo correcto.Aquí establecemos un límite superior para el tipo T
, O en otras obras T
Debería ser un subtipo Animal
.
¡No está mal, genial! Podemos tener un tipo más, por ejemplo,
Jungle<T,U,V>
?
Sí tu puedes. Puede tener cualquier número, pero un buen diseño es limitarlo a 2. Más solo provocará confusión.Además, tenga en cuenta in
con out
Puede aplicarse de forma independiente a este tipo de variables.Entonces puedes tener algo como Jungle<in T, out U, in V : Animal>
. Por eso cuando hablas de Covarianza o Inversor, También debe mencionar las variables de tipo. aquí, Jungle
sí Covarianza existe U
, pero Contravariante existe T
con V
.
F
out
,in
con:
Establece un límite superior.que esJungle<*>
¿Luego? Estoy confundido.
Tu usas *
Supongamos que no conoce el tipo, pero aún desea usarlo de manera segura.Estos se llaman Proyección de estrellas. Suponga que tiene un List
Cosa, no sabes de qué tipo es, y luego usas List<*>
Esto tiene diferentes significados basados en la varianza.Si solo puedes leer, entonces *
Significa que puedes leer Any?
, Si solo puedes escribir, significa que puedes escribir Nothing
. Piénsalo.Si no sabe nada sobre Type, solo puede leer los tipos básicos de todas las extensiones de Type Any?
. Cuando tienes que escribir, no puedes escribir nada porque no conoces el tipo. Las siguientes son situaciones diferentes basadas en la variación:
Jungle<out T: Animal>
: aquíJungle<*>
métodoJungle<out Animal>
, Es decir, puedes leerAnimal
DeJungle<*>
Jungle<in T
>:Jungle<*>
métodoJungle<in Nothing>
, Es decir, no puedes escribir nadaJungle<*>
Jungle<T: Animal>
: aquíJungle<*>
métodoJungle<out Animal>
Al leer yJungle<in Nothing>
Cuando se escribe.Jungle<T>
: aquíJungle<*>
métodoJungle<out Any?>
Al leer yJungle<in Nothing>
Cuando se escribe.
En el caso de varios tipos de variables, puede utilizar *
Para cualquier tipo de variable.Por ejemplo, para Jungle<in T, out U, in V : Animal>
, Un posible caso de proyección de estrellas puede ser Jungle<*, Int, *>
.
Oye, escuché algo sobre
where
Palabras clave. ¿Así es como ocurre?
Entonces viste el uso de :
Cuando desee establecer un límite superior.Suponga que tiene que establecer varios límites superiores para el mismo tipo de variable, por ejemplo Jungle<T>
con T
Debería ser un subtipo Animal
con Walkable
Y luego usa where
:
class Jungle<T>(var t: T) where T: Animal, T: Walkable
fun obtainType(): T = t
fun insertType(t: T)
Eso es interesante. ¿Qué pasa con las funciones genéricas? Tiene alguna idea sobre esto?
Sí, incluso si no usa tipos genéricos en clases o interfaces, puede aplicarlos directamente a las funciones.No puedes usar in
con out
, Pero puede establecer el límite superior de la variable de tipo :
o where
Incluso puede tener varias variables de tipo.
fun <T> getAnimals(t: T): List<T> where T: Animal, T: Walkable
return emptyList()
Wow, muchas gracias. Ahora todas mis dudas están resueltas. ¿Me estoy perdiendo algo en los genéricos?
Tener un concepto Borrado de tipo con Parámetros de tipo reificadoPero creo que este es otro día. ¡Mantente a salvo y vuélvete loco!