Generics
https://kotlinlang.org/docs/generics.html |
22.02.2018 | https://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?
, но не можете изменять список.
- Проекция для входных типов (как в
List<*>
)
Когда используется List<*>
, это эквивалентно List<out Any?>
. Это означает, что вы можете безопасно читать элементы как тип Any?
, но вы не можете добавлять элементы в этот список, так как компилятор не знает, какой конкретный тип должен быть в списке.
fun printContents(list: List<*>) {
for (item in list) {
println(item)
}
}
- Проекция для изменяемых коллекций (как в
MutableList<*>
)
Для изменяемых коллекций, таких как MutableList<*>
, звездная проекция запрещает как чтение с конкретным типом (кроме Any?
), так и запись в коллекцию. Это связано с тем, что тип элемента неизвестен, и компилятор не может гарантировать типовую безопасность.
val mutableList: MutableList<*> = mutableListOf(1, 2, 3)
val first = mutableList[0] // first имеет тип Any?
mutableList.add(4) // Ошибка компиляции: тип неизвестен, нельзя добавить элемент
Вопросы на собесе (5)
- Для чего нужны дженерики в Kotlin?
Дженерики позволяют создавать универсальные классы и функции, работающие с разными типами данных, обеспечивая безопасность типов на этапе компиляции. Это уменьшает количество дублируемого кода и предотвращает ошибки приведения типов.
- Разница между дженериками в Kotlin и Java?
В Kotlin дженерики поддерживают ковариантность и контравариантность с ключевыми словами
out
иin
, что делает их использование гибче по сравнению с Java. Также в Kotlin дженерики сохраняют информацию о типах во время компиляции благодаряinline
-функциям, в то время как в Java происходит стирание типов.
- Как Kotlin решает проблемы со стиранием типов, которые существуют в Java?
Kotlin решает проблему стирания типов с помощью
inline
-функций и ключевого словаreified
, которое сохраняет информацию о типах на этапе выполнения, позволяя выполнять операции с ними внутри функции.
- Что такое инвариантность, ковариантность и контравариантность?
• Инвариантность означает, что типы не могут быть заменены друг другом, даже если они имеют отношения наследования.
• Ковариантность (
out
) позволяет использовать подтипы.• Контравариантность (
in
) позволяет использовать супертипы.
- Что такое звездная проекция в Kotlin и где она используется?
Звездная проекция в Kotlin (
*
) используется для работы с дженериками, когда точный тип неизвестен или не важен. Она позволяет использовать любые возможные типы в обобщениях, предоставляя гибкость при работе с коллекциями и другими обобщенными классами.