Объясни принцип PECS (Producer Extends, Consumer Super) в Java Generics и приведи пример.

Ответ

PECS (Producer Extends, Consumer Super) — это мнемоническое правило для безопасного использования подстановочных типов (wildcards) ? extends и ? super в Java Generics. Оно определяет, какую операцию (чтение или запись) можно безопасно выполнять с коллекцией.

1. Producer Extends (? extends T) Используется, когда коллекция производит (отдает) элементы типа T. С такой коллекцией можно безопасно читать, но нельзя добавлять новые элементы (кроме null).

// Коллекция — производитель Number
List<? extends Number> numbers = Arrays.asList(1, 2.5, 3L);

// БЕЗОПАСНО: Чтение. Все элементы можно трактовать как Number.
Number first = numbers.get(0);
Double second = (Double) numbers.get(1); // После проверки
for (Number n : numbers) { /* ... */ }

// НЕБЕЗОПАСНО / ОШИБКА КОМПИЛЯЦИИ: Запись.
// numbers.add(10);   // Нельзя добавить Integer
// numbers.add(3.14); // Нельзя добавить Double
// Компилятор не знает точный тип внутри List.

2. Consumer Super (? super T) Используется, когда коллекция потребляет (принимает) элементы типа T. В такую коллекцию можно безопасно записывать объекты T (и его подтипы), но читать можно только как Object.

// Коллекция — потребитель Integer
List<? super Integer> integers = new ArrayList<Number>();

// БЕЗОПАСНО: Запись. Можно добавлять Integer и его подтипы.
integers.add(42);
integers.add(100);

// НЕБЕЗОПАСНО: Чтение с конкретным типом.
// Integer i = integers.get(0); // Ошибка компиляции
// Компилятор знает только, что это некий супертип Integer.

// БЕЗОПАСНО: Чтение как Object.
Object obj = integers.get(0);

Итог:

  • Для чтения используй ? extends T (коллекция — производитель).
  • Для записи используй ? super T (коллекция — потребитель). Это правило критически важно для написания типобезопасных обобщенных методов, таких как Collections.copy().

Ответ 18+ 🔞

Давай разжую тебе эту тему, а то смотрю, глаза стекленеют, как у пьяного ежа в тумане. PECS, блядь. Не какая-то там штука для фитнеса, а правило, чтобы твои дженерики не выстрелили тебе в ногу в самый ответственный момент.

Представь, есть у тебя коробка. И в ней что-то лежит. PECS — это инструкция, как с этой коробкой можно ебаться, чтобы не обосраться.

1. Producer Extends (? extends T) — Коробка-ДАВАЙКА

Ситуация: коробка производит для тебя штуки типа T. Ты из неё ТОЛЬКО берешь. Как холодильник с пивом — открыл, взял, закрыл.

// Коробка, в которой намешано Number: Integer, Double, Long — хуй пойми что, но всё — Number.
List<? extends Number> numbers = Arrays.asList(1, 2.5, 3L);

// МОЖНО, сука, брать! Всё, что достанешь, будет хотя бы Number. Это безопасно.
Number first = numbers.get(0); // Взял — и не парься.
for (Number n : numbers) { /* делай с ним что хочешь */ }

// НЕЛЬЗЯ, блядь, класть! Абсолютно НИ-ХУ-Я!
// numbers.add(10);   // Ошибка компиляции! А вдруг внутри List<Double>?
// numbers.add(3.14); // Та же хуйня! А вдруг внутри List<Integer>?
// Компилятор не ебёт, что у тебя в голове. Он видит "? extends" и говорит: "Чувак, я понятия не имею, какой точный тип внутри. Поэтому класть туда нихуя нельзя, кроме null. Иди нахуй со своим добавлением".

Короче, ? extends — это когда коробка говорит: «Бери, пожалуйста, но совать своё — иди нахуй». Только чтение.

2. Consumer Super (? super T) — Коробка-ПРИНИМАЙКА

Ситуация: коробка потребляет штуки типа T. Ты в неё ТОЛЬКО кладешь. Как почтовый ящик для писем — кидаешь и забываешь.

// Объявили коробку, которая принимает Integer и всё, что выше (Number, Object).
List<? super Integer> integers = new ArrayList<Number>();

// МОЖНО, ёпта, класть! Integer? Окей. Его наследника? Тоже окей.
integers.add(42); // Идеально.
integers.add(100); // Без проблем.

// НЕЛЬЗЯ, сука, нормально ЧИТАТЬ! Вернее, прочитать-то можно, но...
// Integer i = integers.get(0); // ОШИБКА! А вдруг там лежит Object или Number?
// Компилятор орёт: "Э, бошка думай! Я знаю только, что это '? super Integer'. Может, там Object лежит? Нахуй твое приведение типов!"

// МОЖНО читать, но только как полнейшую абстракцию — Object. На что похоже? На выигрыш в лотерею, который оказался фантиком.
Object obj = integers.get(0); // Да, вот так. Пиздецкая полезность, да?

Итог по ? super: коробка говорит: «Давай сюда свои Integer, я всё проглочу. Но что я отрыгну обратно — не обещаю, может, просто Object». В основном для записи.

Так нахуя это всё?

А нахуя, спрашиваешь? Чтобы писать методы, которые не ебут мозг тем, кто будет их использовать. Классический пример — Collections.copy(dest, src).

  • dest (куда копируем) должен ПРИНИМАТЬ элементы из src. Значит, ему ? super T.
  • src (откуда копируем) должен ОТДАВАТЬ элементы. Значит, ему ? extends T.

Вот и весь PECS, ёпта. Producer EXTENDS, Consumer SUPER. Запомни эту хуйню, и компилятор перестанет материться на тебя красными буквами. А то ведь реально, как с той Муму: хотел как лучше, а получил ClassCastException на ровном месте.