Generics

https://kotlinlang.org/docs/generics.html
22.02.2018https://youtu.be/HLkjmO4_WeM
Generics

Обобщения (generics) позволяют создавать классы и функции, которые работают с разными типами данных, не привязываясь к конкретному типу на этапе разработки. Это делает код гибким и универсальным.

Ограничения обобщений (constraints) задают, какие типы можно использовать в качестве параметров. По умолчанию все типы ограничены Any?, что позволяет использовать любой объект, включая null. Например, <T> равнозначно <T: Any?>.

open class Grandpa // Дед

open class Dad: Grandpa() // Батя

class Son: Dad() // Сынок
CovarianceКовариантностьЕсли есть тип T (батя), то разрешены эти типы и его подтипы (сынок).Есть налог на людей без внуков, платить батя и сынок.
ContravarianceКонтравариантностьЕсли есть тип T (батя), то разрешены эти типы и его супертипы (дед).Только взрослые могут пить пивас, пьют батя и дед.
InvarianceИнвариантностьЕсли есть тип T (батя), то разрешены эти типы и никаки другие.Скидка для людей, у которых есть родители и дети, скидку получает только батя.
BivarianceБивариантностьЕсли есть тип T (батя), то разрешены как подтипы, так и супертипыСкидка для всех мужиков, скидку получают дед батя и сынок.
out

Используется для ковариантности. Например, List<out T> позволяет передавать List<String> там, где ожидается List<Any>.

interface Producer<out T> {
    fun produce(): T
}
in

Используется для контравариантности. Например, Consumer<in Number> принимает Int, Double и другие подтипы Number.

interface Consumer<in T> {
    fun consume(item: T)
}
where

Используется для добавления ограничений на типы параметров.

fun <T> process(list: List<T>) where T: Comparable<T>, T: Serializable {
    // обработка списка
}
star-projection (*)

Звездная проекция. Используется в дженериках для указания, что вы не знаете или не хотите явно указывать точный тип параметра типа, но при этом хотите сохранить типовую безопасность. Звездная проекция фактически означает “любой допустимый тип” с определенными ограничениями.

• Для ковариантных типов (например, List<out T>), List<*> интерпретируется как List<out Any?>. Это означает, что элементы можно безопасно получать (как Any?), но нельзя изменять.

• Для контравариантных типов (например, Comparable<in T>), Comparable<*> интерпретируется как Comparable<Nothing>. Это значит, что вы не можете передать в него значение, потому что нет допустимых значений для Nothing.

• Для инвариантных типов (например, MutableList<T>), MutableList<*> интерпретируется так, что вы можете только получать элементы как Any?, но не можете изменять список.

  1. Проекция для входных типов (как в List<*>)

Когда используется List<*>, это эквивалентно List<out Any?>. Это означает, что вы можете безопасно читать элементы как тип Any?, но вы не можете добавлять элементы в этот список, так как компилятор не знает, какой конкретный тип должен быть в списке.

fun printContents(list: List<*>) {
    for (item in list) {
        println(item)
    }
}
  1. Проекция для изменяемых коллекций (как в MutableList<*>)

Для изменяемых коллекций, таких как MutableList<*>, звездная проекция запрещает как чтение с конкретным типом (кроме Any?), так и запись в коллекцию. Это связано с тем, что тип элемента неизвестен, и компилятор не может гарантировать типовую безопасность.

val mutableList: MutableList<*> = mutableListOf(1, 2, 3)
val first = mutableList[0]  // first имеет тип Any?
mutableList.add(4)  // Ошибка компиляции: тип неизвестен, нельзя добавить элемент
Вопросы на собесе (5)
  1. Для чего нужны дженерики в Kotlin?

    Дженерики позволяют создавать универсальные классы и функции, работающие с разными типами данных, обеспечивая безопасность типов на этапе компиляции. Это уменьшает количество дублируемого кода и предотвращает ошибки приведения типов.

  1. Разница между дженериками в Kotlin и Java?

    В Kotlin дженерики поддерживают ковариантность и контравариантность с ключевыми словами out и in, что делает их использование гибче по сравнению с Java. Также в Kotlin дженерики сохраняют информацию о типах во время компиляции благодаря inline-функциям, в то время как в Java происходит стирание типов.

  1. Как Kotlin решает проблемы со стиранием типов, которые существуют в Java?

    Kotlin решает проблему стирания типов с помощью inline-функций и ключевого слова reified, которое сохраняет информацию о типах на этапе выполнения, позволяя выполнять операции с ними внутри функции.

  1. Что такое инвариантность, ковариантность и контравариантность?

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

    Ковариантность (out) позволяет использовать подтипы.

    Контравариантность (in) позволяет использовать супертипы.

  1. Что такое звездная проекция в Kotlin и где она используется?

    Звездная проекция в Kotlin (*) используется для работы с дженериками, когда точный тип неизвестен или не важен. Она позволяет использовать любые возможные типы в обобщениях, предоставляя гибкость при работе с коллекциями и другими обобщенными классами.