Ответ
В Kotlin дженерики по умолчанию инвариантны (out и in не указаны). Это значит, что Box<Cat> не является ни подтипом, ни надтипом Box<Animal>, даже если Cat наследует Animal.
Пример инвариантного дженерика:
class Box<T>(var item: T)
val catBox: Box<Cat> = Box(Cat())
val animalBox: Box<Animal> = catBox // Ошибка компиляции!
Для ковариантности (Box<Cat> → Box<Animal>) используется out:
class Box<out T>(val item: T)
Для контравариантности (Box<Animal> → Box<Cat>) — in:
class Box<in T>(fun consume(item: T)) Ответ 18+ 🔞
Э, слушай, а вот смотри, как тут у нас в Kotlin с дженериками приколы-то. Ты ж думаешь, раз Cat — это Animal, то и коробка с котиком автоматически станет коробкой с животным? Хуй там! По умолчанию-то они, блядь, инвариантные, как будто на дворе 2002-й год и все боятся дженериков как огня. Это значит, что Box<Cat> — это вообще отдельная, ёпта, вселенная, и Box<Animal> к ней нихуя не относится. Ни папа, ни сын, просто соседи через забор.
Вот смотри, простой пример, инвариантный класс:
class Box<T>(var item: T)
val catBox: Box<Cat> = Box(Cat())
val animalBox: Box<Animal> = catBox // Ошибка компиляции! Ёперный театр!
Компилятор тебе сразу: «Чувак, ты чё, охуел?» Он тебе не даст это сделать, потому что Box<T> — это как сейф с одним типом. Если ты попробуешь подсунуть сейф для кошек под сейф для животных, он тебе скажет: «А вдруг ты потом в этот «животный» сейф засунешь Dog, а он на самом деле для Cat? Тогда будет тебе, блядь, хиросима и нигерсраки!» И он прав, сука.
Но если тебе нужно, чтобы работало, то есть два выхода, ядрёна вошь!
Первый — ковариантность (out). Это когда ты говоришь: «Э, компилятор, расслабься! Я из этой коробки только вынимать буду, ничего туда не засуну!» Ставишь out — и вуаля, Box<Cat> становится подтипом Box<Animal>.
class Box<out T>(val item: T) // out — только на выход, как из магазина
Второй — контравариантность (in). Это вообще, блядь, мозголомная штука. Ты говоришь: «Эта штука не производит T, она его потребляет!» И тогда всё идёт наоборот: Box<Animal> становится подтипом Box<Cat>. Звучит как пиздец, но это работает, когда тебе нужно что-то засунуть внутрь, а не достать.
class Box<in T> {
fun consume(item: T) { /* ... */ } // in — только на вход, как в мясорубку
}
Короче, суть в том, что инвариантность по умолчанию — это такая хитрая жопа для твоей же безопасности, чтобы ты случайно не впендюрил собаку в коробку для кошек. А out и in — это твои пропуски, чтобы сказать системе: «Я знаю, что делаю, доверия ебать ноль, но вот мои гарантии». Вот и вся магия, не так страшен чёрт, как его малюют.