Ответ
PECS (Producer Extends, Consumer Super) — принцип работы с дженериками в Java/Kotlin, помогающий избежать ошибок типизации.
- Producer Extends (
? extends T) — используется, когда коллекция только производит элементы (например, для чтения). Позволяет читать элементы какT, но не добавлять.
fun printNumbers(list: List<out Number>) {
list.forEach { println(it) } // Можно читать
// list.add(1) — ошибка компиляции
}
- Consumer Super (
? super T) — применяется, когда коллекция потребляет элементы (например, для записи). Можно добавлятьTи его подтипы, но читать только какAny?.
fun addNumbers(list: MutableList<in Number>) {
list.add(42) // Можно писать
val item = list[0] // Тип — Any?
}
PECS улучшает безопасность типов и гибкость API.
Ответ 18+ 🔞
А, слушай, вот эта тема с PECS — это вообще отдельная песня, блядь. Сидишь такой, пишешь код, и тут бац — компилятор начинает орать, как резаный, потому что ты пытаешься запихнуть Double в List<Number>, который на самом деле List<Integer>. И ты сидишь, чешешь репу: «Какого хуя? Он же extends Number!».
Так вот, чувак, чтобы не было таких вот пиздопроебибных ситуаций, умные дядьки и придумали этот принцип. Суть проще, чем кажется, если не загоняться.
Представь, что у тебя есть коробка. PECS — это правила, что ты можешь с ней делать, в зависимости от того, что на ней написано.
Producer Extends (? extends T) — это когда коробка только отдаёт хуйню. Написано, например, List<? extends Number>. Это значит, что внутри лежат какие-то потомки Number: Integer, Double, Float — хуй с горы, не важно какие. Главное — что ты из этой коробки можешь спокойно доставать и быть уверенным, что это хотя бы Number.
fun printNumbers(list: List<out Number>) {
list.forEach { println(it) } // Всё ок, читаем как Number
// list.add(1) // А вот это — НИ-ХУ-Я! Ошибка компиляции!
}
Почему нельзя добавить? Да потому что компилятор — не телепат, ёпта! Он видит List<? extends Number>. А что там внутри на самом деле? Может, List<Double>? А ты лезешь со своим Integer. И если он тебе это разрешит, то потом, когда кто-то будет читать из этого списка Double, получит пиздец вместо числа. Поэтому он тебя и останавливает: «Не-не-не, браток, отсюда только читай. Producer, блядь, Extends. Производитель».
Consumer Super (? super T) — обратная история. Это когда коробка только принимает хуйню. Написано MutableList<? super Number>. Это значит, что коробка предназначена для Number или его предков (например, Any). И в такую коробку ты можешь положить любого потомка Number: и Integer, и Double.
fun addNumbers(list: MutableList<in Number>) {
list.add(42) // Ок, Integer extends Number
list.add(3.14) // Ок, Double extends Number
val item = list[0] // А вот читать-то опасно! Тип будет Any? (или Object в Java)
}
Почему читать опасно? Ну смотри: в листе MutableList<in Number> может на самом деле быть MutableList<Any>. Туда можно положить и Number, и String, и вообще что угодно. Когда ты пытаешься достать элемент, компилятор тебе честно говорит: «Чувак, я нихуя не знаю, что там, кроме того, что это Any?. Разбирайся сам потом, на свой страх и риск».
Вот и весь принцип, блядь. PECS (Producer Extends, Consumer Super):
- ЧИТАЕШЬ (Producer) — используй
extends(outв Kotlin). Безопасно прочитать как родителя. - ПИШЕШЬ (Consumer) — используй
super(inв Kotlin). Безопасно записать потомка.
Применяешь это — и волнение ебать уходит, компилятор перестаёт материться, код становится гибким и типобезопасным. А иначе — сам от себя охуеешь, когда в рантайме вылезет ClassCastException на ровном месте.