Generics

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

Generics

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

open class Grandpa // Дед

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

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

out

Определяет ковариантность в месте объявления. Делает параметризованный тип ковариантным. Если T - Any, то разрешены также все наследники Any

interface Source<out T: Any> {
    fun nextT(): T
}

fun demo(strings: Source<String>) {
    val objects: Source<Any> = strings // Всё в порядке, т.к. T — out-параметр
}
val value: List<Any> = listOf(1, 2, 3) // since List signature is List<out T> in Kotlin

in

Определяет контравариантность в месте объявления. Делает параметризованный тип контравариантным. Если T - Double, то разрешен также Number

interface Comparable<in T> {
    operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 имеет тип Double, расширяющий Number
    val y: Comparable<Double> = x // Можем присвоить значение x переменной типа Comparable<Double>
}

where

Используется для указания нескольких ограничений типов

class Person<T>(
    val id: T, 
    val name: String
)

fun <T> display(obj: T) {
    println(obj)
}

val tom: Person<Int> = Person(367, "Tom")
val bob: Person<String> = Person("A65", "Bob")

display("Hello Kotlin")
display(1234)
display(true)

fun <T: Comparable<T>> getBiggest(a: T, b: T): T {
    return if (a > b) a else b
}

fun <T> getBiggest(a: T, b: T): T where T: Comparable<T>, T: Number {
    return if (a > b) a else b
}

class Messenger<T> where T: Serializable, T: Parcelable {
    fun convert(obj: T) {}
}

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)  // Ошибка компиляции: тип неизвестен, нельзя добавить элемент

Kotlin Generics. Вопросы на собесе
  1. Что такое дженерики (generics) в Kotlin?
  1. Объясните основные различия между дженериками в Kotlin и Java?
  1. Как Kotlin решает проблемы с «Type Erasure» (стиранье типов), которые существуют в Java?
  1. Что такое инвариантность, ковариантность и контравариантность?
  1. Объясните, что такое звездная проекция в Kotlin и где она используется?