Я изучал модификаторы in and out в Котлине. Рассматривая это с точки зрения Java, документация подкрепляет подход сопоставление концепции с Java, и я не уверен, что это лучший способ, потому что мне и другим было трудно понять его. in и out в объявлении вроде fun computeSomething(List<out T>) немного похожи на <? super T> и <? extends T> в Java, но они не совсем одинаковы, и я думаю, что это потому, что, хотя компилятор Kotlin в конечном итоге сопоставляет их с теми же концепциями JVM, которые доступны для Java, это также выполняет свои собственные проверки во время компиляции, которые не совсем совпадают.
Вместо того, чтобы пытаться сопоставить их с Java и на высоком уровне, я думаю, что вот что означают эти модификаторы. Я пишу это отчасти для того, чтобы лучше понять это, поэтому, пожалуйста, потерпите меня, если я не уловлю всех нюансов, и дайте мне знать, если я ошибаюсь.
out : я собираюсь извлечь значение вне отсюда. Он создается с помощью этого кода. Это полезно для классов и функций, которые вычисляют результат.
interface Producer<out T> {
fun produce(): T
}
Я могу присвоить значение Producer<String> объекту Producer<Any>, потому что produce() будет извлекать значение, которое можно присвоить объекту Any.
in: Я собираюсь отправить сюда в значение. Он используется этим кодом. Это полезно, когда вы читаете свойства объекта и вычисляете что-то еще, а не сохраняете и не создаете эти объекты.
interface Consumer<in T> {
fun consume(thing: T)
}
Реализации Comparable просто считывают T и не создают их.
Используя интерфейсы выше:
class IntProducer: Producer<Int> {
override fun produce() = (Math.random() * 1000).toInt()
}
class Printer<in T>: Consumer<T> {
override fun consume(thing: T) {
println(thing)
}
}
fun inOutTest() {
val numberProducer: Producer<Number> = IntProducer()
val numberPrinter = Printer<Number>()
numberPrinter.consume(numberProducer.produce())
}
Однако этот код не компилируется:
interface NumberProducer: Producer<Number>
fun fullOfErrors() {
val numberProducer: NumberProducer = IntProducer() // wrong type
}
Интересным последствием того, как работают in и out, является то, что out имеет тенденцию быть в неизменяемых коллекциях, и ни один из них не работает с изменяемыми коллекциями, которые также дают результат.
Проверьте этот код:
interface ImmutableStack<out T> {
fun peek(): T
}
interface GrowingStack<in T> {
fun push(item: T)
}
interface ShrinkingStack<out T> {
fun pop(): T
}
class Stack<T>: ImmutableStack<T>, GrowingStack<T>, ShrinkingStack<T> {
private val items: MutableList<T>
constructor(items: Iterable<T>) {
this.items = items.toMutableList()
}
override fun peek() = items.last()
override fun push(item: T) {
items.add(item)
}
override fun pop(): T = items.removeLast()
}
Наличие отдельных интерфейсов для GrowingStack и ShrinkingStack выглядит нелепым излишеством, но если я попытаюсь их объединить, то не смогу иметь push() и pop() в одном и том же interface или class, пока не удалю in и out.
Приведенный ниже код приведет к ошибке Type parameter T is declared as ‘out’ but occurs in ‘in’ position in type T в push(), пока я не удалю out, а если я заменю его на in, то в других функциях произойдет обратное.
class MutableStack<out T>: ImmutableStack<T> {
private val items: MutableList<T>
constructor(items: Iterable<T>) {
this.items = items.toMutableList()
}
override fun peek(): T = items.last()
fun push(item: T) = items.add(item)
fun pop(): T = items.removeLast()
}
Я предполагаю, что это означает, что все изменяемые классы коллекций имеют параметры типа, которые не являются ни in, ни out. Это, вероятно, ограничивает помощь, которую может дать компилятор. Тем не менее, продолжайте помещать out в эти неизменяемые коллекции, чтобы вы могли вернуть Int из какого-то Collection<Int>, где ожидается Number.