Co to jest słowo kluczowe w kotlin

86

Nie jestem w stanie zrozumieć i nie mogłem znaleźć znaczenia słowa kluczowego out w kotlin.

Tutaj możesz sprawdzić przykład:

List<out T>

Jeśli ktoś może wyjaśnić znaczenie tego. Byłoby to naprawdę mile widziane.

Akshay Sood
źródło

Odpowiedzi:

58

Z tym podpisem:

List<out T>

możesz to zrobić:

val doubleList: List<Double> = listOf(1.0, 2.0)
val numberList: List<Number> = doubleList

co oznacza, że T jest kowariantne :

gdy parametr typu T klasy C deklaruje się , C <Podstawa> może być bezpiecznie supertypem od C <Pochodzące> .

Jest to kontrast z w , na przykład

Comparable<in T>

możesz to zrobić:

fun foo(numberComparable: Comparable<Number>) {
  val doubleComparable: Comparable<Double> = numberComparable
  // ...
}

co oznacza, że T jest sprzeczne :

gdy parametr typu T klasy C jest zadeklarowany w , C <Pochodzące> może być bezpiecznie supertypem od C <base> .

Inny sposób, aby to zapamiętać:

Konsument w , producent out .

zobacz Kotlin Generics Variance

----------------- aktualizacja 4 stycznia 2019 r. -----------------

W przypadku „ Consumer in, Producer out ”, czytamy tylko z metody Producer - call, aby uzyskać wynik typu T; i tylko pisz do Konsumenta - wywołaj metodę przekazując parametr typu T.

W przykładzie dla List<out T>jest oczywiste, że możemy to zrobić:

val n1: Number = numberList[0]
val n2: Number = doubleList[0]

Więc jest to bezpieczne, List<Double>kiedy List<Number>się tego oczekuje, stąd List<Number>jest super typ List<Double>, ale nie odwrotnie.

W przykładzie dla Comparable<in T>:

val double: Double = 1.0
doubleComparable.compareTo(double)
numberComparable.compareTo(double)

Więc jest to bezpieczne, Comparable<Number>kiedy Comparable<Double>się tego oczekuje, stąd Comparable<Double>jest super typ Comparable<Number>, ale nie odwrotnie.

Andrew Feng
źródło
1
Myślę, że najważniejszą kwestią dla jednej widzącej List<out T>deklaracji jest to, że outczyni ją niezmienną (w porównaniu ze zmiennymi kolekcjami, które nie mają wyjścia). Pomocne może być wspomnienie i podkreślenie tego w odpowiedzi. Rzutowanie niejawne jest konsekwencją tego, a nie głównego punktu (ponieważ nie można pisać do List <Number>, bezpiecznie jest mieć to jako odniesienie do List <Double>).
Mińsk
41
Przepraszam, ale nadal nie mogłem zrozumieć.
Akshay Taru
2
@minsk Jednak outto nie ta część sprawia, że ​​jest Listniezmienny. Możesz łatwo stworzyć własny List<out T>interfejs, który ma clear()metodę, ponieważ nie wymagałby żadnych argumentów.
Nick Lowery
jest to jeden z tych tematów, które prawdopodobnie chcesz przeczytać, dopóki naprawdę nie będziesz go potrzebować w swoim kodzie.
lasec0203
111
List<out T> is like List<? extends T> in Java

i

List<in T> is like List<? super T> in Java

Na przykład w Kotlinie możesz robić takie rzeczy jak

 val value : List<Any> = listOf(1,2,3)
//since List signature is List<out T> in Kotlin
DmitryBorodin
źródło
4

Zapoznaj się z instrukcją kotlin

Typ Kotlin List<out T>to interfejs, który zapewnia operacje tylko do odczytu, takie jak size, get i tak dalej. Podobnie jak w Javie, dziedziczy on z, Collection<T>a to z kolei dziedziczy z Iterable<T>. MutableList<T>Interfejs dodaje metody zmieniające listę . Ten wzór dotyczy również Set<out T>/MutableSet<T>iMap<K, out V>/MutableMap<K, V>

I to,

W Kotlinie istnieje sposób na wyjaśnienie tego typu rzeczy kompilatorowi. Nazywa się to wariancją witryny deklaracji: możemy dodać adnotację do parametru typu T źródła, aby upewnić się, że jest on zwracany (produkowany) tylko od elementów członkowskich Source<T>i nigdy nie jest używany. Aby to zrobić, podajemy modyfikator 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
>     // ... }

Ogólna zasada jest taka: kiedy parametr typu Tklasy Cjest zadeklarowany jako out, może wystąpić tylko w pozycji wyjściowej w elementach członkowskich C, ale w zamian C<Base>może bezpiecznie być nadtypem C<Derived>.

W „sprytnych słowach” mówią, że klasa Cjest kowariantna w parametrze Tlub że Tjest to parametr typu kowariantnego. Możesz myśleć o C jako o producencie T, a NIE o konsumentach T. Modyfikator out jest nazywany adnotacją wariancji, a ponieważ jest dostarczany w miejscu deklaracji parametru typu, mówimy o wariancji witryny deklaracji. Kontrastuje to z wariancją miejsca użycia w Javie, gdzie symbole wieloznaczne w zastosowaniach typów powodują, że typy są kowariantne.

LF00
źródło
3

Pamiętaj w ten sposób:

into „for in put” - chcesz coś do niego włożyć (napisać) (więc jest to „konsument”)

outjest „na wyciągnięcie ręki” - chcesz coś z tego wyciągnąć (przeczytać) (więc jest to „producent”)

Jeśli jesteś z Javy,

<in T>służy do wprowadzania danych, więc jest jak <? super T>(konsument)

<out T>jest na wyjście, więc jest jak <? extends T>(producent)

starriet
źródło
0

Modyfikatory wariancji outi inpozwalają nam uczynić nasze typy ogólne mniej restrykcyjnymi i łatwiejszymi do ponownego użycia, umożliwiając podtytuł.

Zrozummy to za pomocą kontrastujących przykładów. Użyjemy przykładów skrzyń jako pojemników z różnymi rodzajami broni. Załóżmy, że mamy następującą hierarchię typów:

open class Weapon
open class Rifle : Weapon()
class SniperRifle : Rifle()

outprodukuje Ti zachowuje podtypy

Kiedy deklarujesz typ ogólny z outmodyfikatorem, nazywa się to kowariantem . Kowariantna to producent z T, co oznacza, że funkcje mogą wrócić T, ale nie mogą brać Tjako argumenty:

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
}

CaseZadeklarowana z outmodyfikator produkuje Ti jego podtypy:

fun useProducer(case: Case<Rifle>) {
    // Produces Rifle and its subtypes
    val rifle = case.produce()
}

W przypadku outmodyfikatora podtyp jest zachowywany , więc Case<SniperRifle>jest podtypem Case<Rifle>kiedy SniperRiflejest podtypem Rifle. W rezultacie useProducer()funkcję można wywołać Case<SniperRifle>również za pomocą :

useProducer(Case<SniperRifle>())               // OK
useProducer(Case<Rifle>)                       // OK
useProducer(Case<Weapon>())                    // Error

Jest to mniej restrykcyjne i możliwe do ponownego wykorzystania podczas produkcji, ale nasza klasa staje się tylko do odczytu .


inzużywa Ti odwraca podtypy

Kiedy deklarujesz typ ogólny z inmodyfikatorem, jest on wywoływany contravariant. Kontrawariantny jest konsument z T, co oznacza, że funkcje mogą wziąć Tjako argumenty, ale nie mogą wrócić 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
}

CaseOświadczył z inmodyfikatorów zużywa Ti jego podtypy:

fun useConsumer(case: Case<Rifle>) {
    // Consumes Rifle and its subtypes
    case.consume(SniperRifle())
}

Za pomocą inmodyfikatora podtyp jest odwracany , więc teraz Case<Weapon>jest podtypem Case<Rifle>kiedy Riflejest podtypem Weapon. W rezultacie useConsumer()funkcję można wywołać Case<Weapon>również za pomocą :

useConsumer(Case<SniperRifle>())               // Error          
useConsumer(Case<Rifle>())                     // OK
useConsumer(Case<Weapon>())                    // OK

Jest to mniej restrykcyjne i bardziej wielokrotnego użytku podczas konsumowania, ale nasza klasa staje się tylko do zapisu .


Invariant produkuje i konsumuje T, nie pozwala na podtypy

Kiedy deklarujesz typ ogólny bez żadnego modyfikatora wariancji, nazywa się to niezmiennym . Niezmiennik jest zarówno producentem, jak i konsumentem T, co oznacza, że ​​funkcje mogą przyjmować Tjako argumenty, a także zwracać 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
}

CaseZadeklarowana bez inlub outmodyfikator produkuje i konsumuje Ti jego podtypy:

fun useProducerConsumer(case: Case<Rifle>) {
    // Produces Rifle and its subtypes
    case.produce()
    // Consumes Rifle and its subtypes
    case.consume(SniperRifle())
}

Bez modyfikatora inor podtyp outjest niedozwolony , więc teraz ani Case<Weapon>ani nie Case<SniperRifle>jest podtypem Case<Rifle>. W rezultacie useProducerConsumer()funkcję można wywołać tylko za pomocą Case<Rifle>:

useProducerConsumer(Case<SniperRifle>())       // Error
useProducerConsumer(Case<Rifle>())             // OK
useProducerConsumer(Case<Weapon>())            // Error

Jest to bardziej restrykcyjne i mniej wielokrotnego użytku podczas produkcji i konsumpcji, ale możemy czytać i pisać .


Wniosek

W ListKotlin jest tylko producentem. Ponieważ jest zadeklarowana za pomocą outmodyfikatora: List<out T>. Oznacza to, że nie możesz dodawać do niego elementów, ponieważ add(element: T)jest to funkcja konsumencka. Zawsze, gdy chcesz mieć możliwość get()równie dobrze jak add()elementy, użyj niezmiennej wersji MutableList<T>.

Otóż ​​to! Miejmy nadzieję, że to pomoże zrozumieć ins i outs wariancji!

Yogesh Umesh Vaity
źródło