Что такое принципы PECS

Ответ

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 на ровном месте.