Что такое ключевое слово out в котлине

86

Я не могу понять и не могу найти значение ключевого слова out в котлине.

Вы можете проверить пример здесь:

List<out T>

Если кто-нибудь может объяснить значение этого. Было бы очень признательно.

Акшай Суд
источник

Ответы:

58

С этой подписью:

List<out T>

ты можешь это сделать:

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

что означает Т является ковариантным :

когда тип параметра T из класса C объявляется вне , С <База> может безопасно быть супертипом из C <Derived> .

Это контрастирует с в , например ,

Comparable<in T>

ты можешь это сделать:

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

что означает Т является контравариантным :

когда тип параметра T из класса C объявляется в , C <Derived> может безопасно быть супертипом из C <Base> .

Другой способ запомнить:

Потребитель в , продюсер отъезда .

см. Kotlin Generics Variance

----------------- обновлено 4 января 2019 г. -----------------

Для « Потребитель входит, производитель выходит » мы читаем только из источника - вызываем метод, чтобы получить результат типа T; и писать только в метод Consumer - call, передав параметр типа T.

В примере для List<out T>очевидно, что мы можем сделать это:

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

Таким образом, безопасно предоставлять, List<Double>когда List<Number>ожидается, следовательно, List<Number>это супер тип List<Double>, но не наоборот.

В примере для Comparable<in T>:

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

Таким образом, безопасно предоставлять, Comparable<Number>когда Comparable<Double>ожидается, следовательно, Comparable<Double>это супер тип Comparable<Number>, но не наоборот.

Эндрю Фенг
источник
1
Я думаю, что наиболее важным моментом для одного видящего List<out T>объявления является то, что outоно делает его неизменным (по сравнению с изменяемыми коллекциями, у которых нет out). Возможно, стоит упомянуть и подчеркнуть это в ответе. Неявное приведение типов является следствием этого, а не основным моментом (поскольку нельзя писать в List <Number>, безопасно иметь его как ссылку на List <Double>).
минск
41
Извините, но все равно не могу понять.
Акшай Тару
2
@minsk Дело outне в том, что делает Listнеизменяемым. Вы можете легко создать свой собственный List<out T>интерфейс, в котором есть clear()метод, поскольку он не требует аргументов.
Ник Лоури
это одна из тех тем, которые вы, вероятно, захотите получить до тех пор, пока она вам действительно не понадобится где-то в вашем коде.
lasec0203
111
List<out T> is like List<? extends T> in Java

и

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

Например, в Котлине вы можете делать такие вещи, как

 val value : List<Any> = listOf(1,2,3)
//since List signature is List<out T> in Kotlin
ДмитрийБородин
источник
4

Обратитесь к руководству kotlin

Тип Kotlin List<out T>- это интерфейс, который предоставляет операции только для чтения, такие как size, get и т. Д. Как и в Java, он наследуется от, Collection<T>а тот, в свою очередь, наследуется от Iterable<T>. Методы, изменяющие список, добавляются MutableList<T>интерфейсом. Этот образец справедлив также для Set<out T>/MutableSet<T>иMap<K, out V>/MutableMap<K, V>

И это,

В Kotlin есть способ объяснить компилятору подобные вещи. Это называется отклонением на объекте объявления: мы можем аннотировать параметр типа T объекта Source, чтобы убедиться, что он только возвращается (создается) из членов Source<T>и никогда не используется. Для этого мы предоставляем модификатор 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
>     // ... }

Общее правило: когда параметр Tтипа класса Cобъявляется вне, он может встречаться только вне позиции в членах класса C, но взамен C<Base>может безопасно быть супертипом классаC<Derived> .

В «умных словах» они говорят, что класс Cковариантен по параметру T, или что Tэто параметр ковариантного типа. Вы можете думать о С как о производителе Т, а НЕ как о потребителе T. Модификатор out называется аннотацией отклонения, и, поскольку он предоставляется на сайте объявления параметра типа, мы говорим об отклонении на сайте объявления. Это контрастирует с вариативностью сайта использования Java, где подстановочные знаки в использовании типов делают типы ковариантными.

LF00
источник
3

Запомните вот так:

in"for in put" - вы хотите поместить (написать) что-то в него (так что это "потребитель")

outявляется «для из пут» - вы хотите взять (прочитать) что - то из него (так что это «продюсер»)

Если вы с Java,

<in T>предназначен для ввода, поэтому это похоже на <? super T>(потребитель)

<out T>предназначен для вывода, так что это похоже на <? extends T>(продюсер)

Starriet
источник
0

Модификаторы дисперсии outи inпозволяют нам сделать наши универсальные типы менее ограничительными и более пригодными для повторного использования, разрешив создание подтипов.

Давайте разберемся в этом с помощью контрастных примеров. Будем использовать примеры ящиков как контейнеров для различного оружия. Предположим, что у нас есть следующая иерархия типов:

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

outпроизводит Tи сохраняет подтипы

Когда вы объявляете универсальный тип с outмодификатором, он называется ковариантным . Ковариантный является производителем из T, это означает , что функции могут возвращать , Tно они не могут принимать в Tкачестве аргументов:

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
}

CaseОбъявлен с outмодификатором производит Tи его подтипы:

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

С outмодификатором подтип сохраняется , поэтому Case<SniperRifle>is подтип, Case<Rifle>когда SniperRifleявляется подтипом Rifle. В результате useProducer()функция также может быть вызвана Case<SniperRifle>:

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

Это менее ограничительно и более пригодно для повторного использования при производстве, но наш класс становится доступным только для чтения .


inпотребляет Tи меняет подтипирование

Когда вы объявляете универсальный тип с inмодификатором, он вызывается contravariant. Контравариантный является потребителем из T, это означает , что функции могут принимать в Tкачестве аргументов , но они не могут вернуться 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
}

CaseОбъявлен с inмодификаторами потребляет Tи его подтипов:

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

С помощью inмодификатора подтипы меняются местами , поэтому теперьCase<Weapon> подтип это подтип, Case<Rifle>когда Rifleявляется подтипом Weapon. В результате useConsumer()функция также может быть вызвана Case<Weapon>:

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

Это менее ограничительно и более многоразово при использовании, но наш класс становится только для записи .


Инвариант производит и потребляет T, запрещает выделение подтипов

Когда вы объявляете универсальный тип без модификатора дисперсии, он называется инвариантом . Инвариант является производителем и потребителем T, что означает, что функции могут принимать Tкак аргументы, а также возвращать 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
}

В CaseОбъявлено без inили outмодификатора производит и потребляет Tи его подтипы:

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

Без модификатора inили outвыделение подтипов запрещено , поэтому сейчас Case<Weapon>ниCase<SniperRifle> подтип один из подтипов является Case<Rifle>. В результате useProducerConsumer()функция может быть вызвана только с Case<Rifle>:

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

Это более ограничительно и менее пригодно для повторного использования при производстве и потреблении, но мы можем читать и писать .


Заключение

В ListКотлине только производитель. Потому что он объявлен с использованием outмодификатора:List<out T> . Это означает, что вы не можете добавлять к нему элементы, поскольку add(element: T)это функция потребителя. Всякий раз, когда вы хотите иметь возможность не get()хуже add()элементов, используйте инвариантную версиюMutableList<T> .

Это оно! Надеюсь , что помогает понять inс и outS дисперсии!

Йогеш Умеш Вайти
источник