Вариативность с in и out
Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 40 из 57.
Объект List<String> можно передать туда, где ожидается List<Any>, поскольку List только предоставляет элементы. MutableList<String> так использовать нельзя: код, который видит его как MutableList<Any>, может добавить Int в список строк:
fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }Внутри main:
val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a")) // ошибка: MutableList<String> не является MutableList<Any>Результат:
String, String
[x, 42]out T делает обобщённый класс ковариантным: он только выдаёт значения T в качестве возвращаемых значений и никогда не принимает их. Поэтому Producer<Cat> считается Producer<Animal>:
open class Animal(val name: String)
class Cat(name: String) : Animal(name)
class Producer<out T>(private val item: T) {
fun get(): T = item
}
fun greet(p: Producer<Animal>) = "hello ${p.get().name}"Внутри main:
val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))Вывод:
hello Tomin T делает его контравариантным: он только принимает значения T в качестве параметров. Потребитель, который обрабатывает любой Animal, может выступать в роли потребителя Cat:
open class Animal(val name: String)
class Cat(name: String) : Animal(name)
interface Feeder<in T> {
fun feed(animal: T): String
}
class AnimalFeeder : Feeder<Animal> {
override fun feed(animal: Animal) = "fed ${animal.name}"
}
fun feedCats(cats: List<Cat>, feeder: Feeder<Cat>) = cats.map { feeder.feed(it) }Внутри main:
println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))Вывод:
[fed Tom, fed Kit]Компилятор соблюдает направление: параметр типа out не может быть типом параметра метода, а параметр типа in нельзя вернуть. Практическое правило: производитель — out, потребитель — in. Стандартная библиотека делает то же самое: List<out E>, Comparator<in T>. Без какого-либо из этих ключевых слов параметр типа является инвариантным, как в MutableList<E>, который поддерживает и чтение, и запись:
class Producer<out T>(private val item: T) {
fun get(): T = item // нормально: T выходит
// fun set(x: T) { } // ошибка: T объявлен как out
}
interface Feeder<in T> {
fun feed(animal: T): String // нормально: T входит
// fun last(): T // ошибка: T объявлен как in
}Вариативность также можно записывать там, где используется тип. MutableList<in Int> принимает любой изменяемый список, который может принимать значения типа Int, например MutableList<Number>, а MutableList<out Number> позволяет только чтение:
fun fillWith(target: MutableList<in Int>, n: Int) {
for (i in 1..n) target.add(i)
}
fun total(source: MutableList<out Number>) = source.sumOf { it.toDouble() }Внутри main:
val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))Вывод:
[0.5, 1, 2, 3]
6.0Задание
ЛегкоДва интерфейса перемещают элементы: Source<out T> выдаёт элементы с помощью next() (возвращая null, когда он пуст), а Sink<in T> принимает их с помощью accept(item). Они предоставлены. Напишите ListSource<T>(items), который возвращает элементы списка по одному, а затем null; Collector<T>, который сохраняет принятые элементы в списке items, доступном только для чтения; и Counter — это Sink<Any>, который подсчитывает всё принятое. Затем напишите pipe(from, vararg to), который перемещает каждый элемент источника в каждый приёмник и возвращает количество перемещённых элементов.
Предоставленный код передаёт строку слов в Collector<String>, а строку чисел — в Collector<Number>, в обоих случаях также передавая их одному Counter. Это работает благодаря out и in: ListSource<Int> является Source<Number>, а Sink<Any> является Sink<String>. Он выводит количества, обе коллекции, counted: 7 и after the end: null.
Ваш код должен находиться в ListSource.kt, Collector.kt, Counter.kt, Pipe.kt, Source.kt и Sink.kt. В Main.kt находится предоставленный код ввода/вывода, и его нельзя изменять.
Попробуйте сами
fun main() {
// Предоставленный код ввода/вывода: оставьте его как есть
val input = generateSequence(::readLine).toList()
val words = ListSource(input[0].split(" "))
val ints = ListSource(input[1].split(" ").map { it.toInt() })
val counter = Counter()
val allWords = Collector<String>()
val numbers = Collector<Number>()
println("words moved: " + pipe(words, allWords, counter))
println("numbers moved: " + pipe(ints, numbers, counter))
println(allWords.items)
println(numbers.items)
println("counted: ${counter.count}")
println("after the end: ${words.next()}")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПервичные конструкторыБлоки init и валидацияВторичные конструкторыПовторение — читательский билет4Наследование
Открытые классы и подклассыПереопределение членовsuper и конструкторыАбстрактные классыПовторение — каталог сотрудников7Специальные классы
Классы данныхКлассы перечисленийЗапечатанные иерархииequals, hashCode, toStringПовторение — трекер заказов10Паттерны проектирования. Часть 2
Делегирование с помощью byПаттерн декораторПаттерн строительПаттерн состояние2Свойства и инкапсуляция
Пользовательские аксессорыМодификаторы видимостиПриватные сеттерыlazy и lateinitПовторение — банковский счёт5Интерфейсы
Объявление интерфейсовМетоды по умолчаниюМножественные интерфейсыИнтерфейсы как типыПовторение — способы оплаты8Обобщения
Обобщённые функцииОбобщённые классыОграничения типовВариативность с in и outПовторение — очередь с приоритетом11Проект: Управление библиотекой
Книги и читателиВыдача книг3Объекты и компаньоны
Объявления объектовОбъекты-компаньоныКонстанты с constОбъектные выраженияПовторение — билетная касса6Полиморфизм
Динамическая диспетчеризацияПроверка типов и умные приведения типовwhen с типамиФункции-расширенияПовторение — смотритель зоопарка9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Песочница Kotlin