No puedo entender y no pude encontrar el significado de la palabra clave en kotlin.
Puede consultar el ejemplo aquí:
List<out T>Si alguien puede explicar el significado de esto. Esto sera realmente apreciado.
Recuerda así:
in es "for in put" - quieres poner (escribir) algo en él (así que es un "consumidor")
out es "for out put" - quieres sacar (leer) algo de él (así que es un "productor")
Si eres de Java,
<in T> es para entrada, así que es como <? super T> (consumidor)
<out T> es para salida, así que es como <? extends T> (productor)
Los modificadores de varianza de entrada y out in permiten hacer que nuestros tipos genéricos sean menos restrictivos y más reutilizables al permitir la creación de subtipos.
Entendamos esto con la ayuda de ejemplos contrastantes. Usaremos ejemplos de estuches como contenedores de varias armas. Supongamos que tenemos la siguiente jerarquía de tipos:
open class Weapon open class Rifle : Weapon() class SniperRifle : Rifle()out produce T y conserva la subtipificación Cuando declara un tipo genérico con un modificador de out , se llama covariante . Una covariante es productora de T , lo que significa que las funciones pueden devolver T pero no pueden tomar T como argumentos:
class Case<out T> { private val contents = mutableListOf<T>() fun produce(): T = contents.last() // Producer: OK fun consume(item: T) = contents.add(item) // Consumer: Error } El Case declarado con el modificador out produce T y sus subtipos:
fun useProducer(case: Case<Rifle>) { // Produces Rifle and its subtypes val rifle = case.produce() } Con el modificador out , se conserva el subtipo, por lo que Case<SniperRifle> es un subtipo de Case<Rifle> cuando SniperRifle es un subtipo de Rifle . Como resultado, la función useProducer() también se puede llamar con Case<SniperRifle> :
useProducer(Case<SniperRifle>()) // OK useProducer(Case<Rifle>()) // OK useProducer(Case<Weapon>()) // ErrorEsto es menos restrictivo y más reutilizable durante la producción, pero nuestra clase se vuelve de solo lectura .
in consume T e invierte la subtipificación Cuando declara un tipo genérico con un modificador in , se llama contravariant . Una contravariante es un consumidor de T , eso significa que las funciones pueden tomar T como argumentos pero no pueden devolver T :
class Case<in T> { private val contents = mutableListOf<T>() fun produce(): T = contents.last() // Producer: Error fun consume(item: T) = contents.add(item) // Consumer: OK } El Case declarado con el modificador in consume T y sus subtipos:
fun useConsumer(case: Case<Rifle>) { // Consumes Rifle and its subtypes case.consume(SniperRifle()) } Con el modificador in , el subtipo se invierte , por lo que ahora Case<Weapon> es un subtipo de Case<Rifle> cuando Rifle es un subtipo de Weapon . Como resultado, la función useConsumer() también se puede llamar con Case<Weapon> :
useConsumer(Case<SniperRifle>()) // Error useConsumer(Case<Rifle>()) // OK useConsumer(Case<Weapon>()) // OKEsto es menos restrictivo y más reutilizable mientras se consume, pero nuestra clase se vuelve solo de escritura .
T , no permite la subtipificación Cuando declara un tipo genérico sin ningún modificador de varianza, se llama invariante . Un invariante es tanto productor como consumidor de T , lo que significa que las funciones pueden tomar T como argumentos y también pueden devolver T :
class Case<T> { private val contents = mutableListOf<T>() fun produce(): T = contents.last() // Producer: OK fun consume(item: T) = contents.add(item) // Consumer: OK } El Case declarado sin modificador in o out produce y consume T y sus subtipos:
fun useProducerConsumer(case: Case<Rifle>) { // Produces Rifle and its subtypes case.produce() // Consumes Rifle and its subtypes case.consume(SniperRifle()) } Sin el modificador in entrada o out , el subtipo no está permitido , por lo que ahora ni Case<Weapon> ni Case<SniperRifle> son un subtipo de Case<Rifle> . Como resultado, la función useProducerConsumer() solo se puede llamar con Case<Rifle> :
useProducerConsumer(Case<SniperRifle>()) // Error useProducerConsumer(Case<Rifle>()) // OK useProducerConsumer(Case<Weapon>()) // ErrorEsto es más restrictivo y menos reutilizable al producir y consumir, pero podemos leer y escribir .
The List en Kotlin es solo un productor. Porque se declara usando el modificador out : List<out T> . Esto significa que no puede agregarle elementos ya que add(element: T) es una función de consumo. Siempre que desee poder get() y add() elementos, use la versión invariable MutableList<T> .
¡Eso es todo! ¡Esperemos que eso ayude in comprender las entradas y out de la variación!
Estas respuestas explican qué hace out , pero no por qué lo necesita, así que supongamos que no teníamos out en absoluto. Imagine tres clases: Animal, Cat, Dog y una función tomando una lista de Animal
abstract class Animal { abstract fun speak() } class Dog: Animal() { fun fetch() {} override fun speak() { println("woof") } } class Cat: Animal() { fun scratch() {} override fun speak() { println("meow") } } Dado que Dog es un subtipo de Animal , queremos usar List<Dog> como subtipo de List<Animal> , lo que significa que queremos poder hacer esto:
fun allSpeak(animals: List<Animal>) { animals.forEach { it.speak() } } fun main() { val dogs: List<Dog> = listOf(Dog(), Dog()) allSpeak(dogs) val mixed: List<Animal> = listOf(Dog(), Cat()) allSpeak(mixed) } Y eso está bien, el código imprimirá woof woof para los perros y woof meow para la lista mixta.
El problema es cuando tenemos un contenedor mutable. Dado que List<Animal> puede contener Dog y Cat , podemos agregar ambos a MutableList<Animal>
fun processAnimals(animals: MutableList<Animal>) { animals.add(Cat()) // uh oh, what if this is a list of Dogs? } fun main() { val dogs: MutableList<Dog> = mutableListOf(Dog(), Dog()) processAnimals(dogs) // we just added a Cat to a list of Dogs! val d: Dog = dogs.last() // list of Dogs, so return type of .last() is Dog // but this is actually a Cat d.fetch() // a Cat can't fetch, so what should happen here? } No puede considerar con seguridad MutableList<Dog> sea un subtipo de MutableList<Animal> porque puede hacer cosas con este último (insertar un gato) que no puede hacer con el primero.
Como un ejemplo más extremo:
val dogs: MutableList<Dog> = mutableListOf(Dog()) val anything: MutableList<Any> = dogs // now I can add any type I want to the dogs list through the anything list anything.add("hello world") El problema solo ocurre al agregar a la lista, no al leer de ella. Es seguro usar List<Dog> como List<Animal> porque no se puede agregar a List . Esto es lo que nos dice out . out dice "este es un tipo que emito, pero no lo tomo como nuevas entradas que consumo"
Una buena explicación de la programación funcional en Kotlin :
Considere el siguiente código:
sealed class List<out A> object Nil : List<Nothing>() data class Cons<out A>(val head: A, val tail: List<A>) : List<A>()En la clase de declaración Lista, la salida delante del parámetro de tipo A es una anotación de varianza que indica que A es un parámetro covariante o “positivo” de Lista. Esto significa, por ejemplo, que Lista se considera un subtipo de Lista, suponiendo que Perro sea un subtipo de Animal. (De forma más general, para todos los tipos X e Y, si X es un subtipo de Y, entonces Lista es un subtipo de Lista). Podríamos omitir la salida delante de A, lo que haría que Lista fuera invariable en ese parámetro de tipo. Pero fíjate ahora que Nil extiende List. Nada es un subtipo de todos los tipos, lo que significa que, junto con la anotación de varianza, Nil puede considerarse una Lista, una Lista, etc., exactamente como queramos.
Consulte el manual de thie de kotlin
El tipo Kotlin
List<out T>es una interfaz que proporciona operaciones de solo lectura como size, get, etc. Al igual que en Java, hereda deCollection<T>y que a su vez hereda deIterable<T>. La interfazMutableList<T>agrega métodos que cambian la lista. Este patrón también es válido paraSet<out T>/MutableSet<T>yMap<K, outV>/MutableMap<K, V>
Y esto,
En Kotlin, hay una forma de explicar este tipo de cosas al compilador. Esto se denomina varianza del sitio de declaración: podemos anotar el parámetro de tipo T de Source para asegurarnos de que solo lo devuelvan (produzcan) los miembros de
Source<T>y nunca se consuma. Para hacer esto proporcionamos el modificador out:> abstract class Source<out T> { > abstract fun nextT(): T } > > fun demo(strs: Source<String>) { > val objects: Source<Any> = strs // This is OK, since T is an out-parameter > // ... }La regla general es: cuando un parámetro de tipo
Tde una claseCse declara out, puede ocurrir solo en posición out en los miembros deC, pero a cambioC<Base>puede ser un supertipo deC<Derived>.En "palabras inteligentes" dicen que la clase
Ces covariante en el parámetroT, o queTes un parámetro de tipo covariante. Puede pensar en C como un productor de T y NO como un consumidor deTEl modificador de salida se denomina anotación de varianza y, dado que se proporciona en el sitio de declaración de parámetros de tipo, hablamos de la varianza del sitio de declaración. Esto contrasta con la variación del sitio de uso de Java donde los comodines en los usos de tipo hacen que los tipos sean covariantes.
Con esta firma:
List<out T>Puedes hacerlo:
val doubleList: List<Double> = listOf(1.0, 2.0) val numberList: List<Number> = doubleListlo que significa que T es covariante :
cuando se declara out un parámetro de tipo T de una clase C , C<Base> puede ser con seguridad un supertipo de C<Derived> .
Esto contrasta con en , por ejemplo
Comparable<in T>Puedes hacerlo:
fun foo(numberComparable: Comparable<Number>) { val doubleComparable: Comparable<Double> = numberComparable // ... }lo que significa que T es contravariante :
cuando un parámetro de tipo T de una clase C se declara en , C<Derived> puede ser con seguridad un supertipo de C<Base> .
Otra forma de recordarlo:
Consumidor adentro , Productor afuera .
ver Variación de los genéricos de Kotlin
-----------------actualizado el 4 de enero de 2019-----------------
Para el " Consumidor dentro, Productor fuera ", solo leemos del Productor - método de llamada para obtener el resultado de tipo T; y solo escriba en el método de llamada al consumidor pasando el parámetro de tipo T.
En el ejemplo de List<out T> , es obvio que podemos hacer esto:
val n1: Number = numberList[0] val n2: Number = doubleList[0] Por lo tanto, es seguro proporcionar List<Double> cuando se espera List< List<Number> , por lo que List<Number> es un supertipo de List<Double> , pero no al revés.
En el ejemplo de Comparable<in T> :
val double: Double = 1.0 doubleComparable.compareTo(double) numberComparable.compareTo(double) Por lo tanto, es seguro proporcionar Comparable<Number> cuando se espera Comparable< Comparable<Double> , por lo que Comparable<Double> es un supertipo de Comparable<Number> , pero no al revés.
List<out T> en Kotlin es equivalente a List<? extends T> en Java.
List<in T> en Kotlin es equivalente a List<? super T> en Java
Por ejemplo, en Kotlin puedes hacer cosas como
val value : List<Any> = listOf(1,2,3) //since List signature is List<out T> in Kotlin