Ответ
Наследование с дженериками в Java не является интуитивным. Хотя класс Integer наследуется от Number, контейнер ArrayList<Integer> НЕ является подтипом ArrayList<Number>. Это обеспечивает безопасность типов во время компиляции.
Проблема:
List<Number> numbers = new ArrayList<Integer>(); // Ошибка компиляции!
// Если бы это было разрешено, можно было бы добавить Double в список Integer:
numbers.add(3.14); // Нарушение типобезопасности!
Решение: Wildcards (подстановочные знаки)
Для работы с иерархиями в дженериках используются ? extends (ковариантность) и ? super (контравариантность).
-
? extends T(Producer - читает): Можно безопасно читать элементы какT.List<? extends Number> numbers = new ArrayList<Integer>(); // OK Number num = numbers.get(0); // Чтение разрешено // numbers.add(new Integer(10)); // Ошибка компиляции - запись запрещена -
? super T(Consumer - пишет): Можно безопасно передавать объекты типаT(или его подтипов) в коллекцию.List<? super Integer> list = new ArrayList<Number>(); // OK // Integer можно добавить, т.к. он является подтипом любого "super" типа (Number, Object) list.add(42); // Запись разрешена // Integer value = list.get(0); // Ошибка компиляции - чтение как Integer небезопасно Object obj = list.get(0); // Чтение как Object разрешено
Правило PECS (Producer-Extends, Consumer-Super):
- Используй
? extends T, если параметр является источником (producer) данных (например, возвращаетT). - Используй
? super T, если параметр является приемником (consumer) данных (например, принимаетT).
Ответ 18+ 🔞
А, ну это классика, блядь! Привет, студент-страдалец! Сел разбираться с дженериками в Java? Ну, держись, щас я тебе на пальцах объясню, как тут всё ебнуто, но логично.
Смотри, вроде бы всё просто: Integer — это Number, да? Как сын — отец. Ну логично же, что ArrayList<Integer> — это тоже типа ArrayList<Number>? А вот нихуя!
List<Number> numbers = new ArrayList<Integer>(); // Компилятор: "Иди нахуй, мудила!"
И он прав, ёпта! Потому что если бы это прокатило, начался бы пиздец. Ты бы в этот, с позволения сказать, список Integer'ов спокойно запихнул Double — и всё, приехали. Runtime ошибка, всё падает, ай-ай-ай. Компилятор этого не любит, он за безопасность, блядь, типобезопасность.
Так вот, чтобы не быть совсем уж мудаком и всё-таки работать с этими семейными связями, придумали дикие карты, wildcards. Это вот эти знаки вопроса ?, от которых глаза на лоб лезут.
Их два основных фокус-покуса:
1. ? extends Твой_Тип (Читатель, Производитель)
Представь, что у тебя есть мешок. Ты точно знаешь, что в нём лежат какие-то наследники Number (может, Integer, может, Double). Ты из этого мешка можешь доставать и быть уверен, что это будет Number. А вот засунуть туда нихуя нельзя! Потому что если мешок для Integer, а ты сунешь Double — опять пиздец.
List<? extends Number> numbers = new ArrayList<Integer>(); // Теперь ок!
Number num = numbers.get(0); // Достать — пожалуйста, отец родной.
// numbers.add(new Integer(10)); // А вот сунуть — НИЗЯ! Компилятор бьёт по рукам.
2. ? super Твой_Тип (Писатель, Потребитель)
А вот обратная история. У тебя есть яма (List). Ты знаешь, что эта яма принимает как минимум Integer и всё, что выше по иерархии (Number, Object). В такую яму ты смело можешь бросать свои Integer (и даже его наследников, если бы они были). А вот достать оттуда конкретный Integer — опасно, хуй знает, что там на самом деле лежит. Максимум — Object.
List<? super Integer> list = new ArrayList<Number>(); // Окей, яма готова.
list.add(42); // Бросай Integer, не стесняйся! Всё схватит.
// Integer value = list.get(0); // А вот так — НИЗЯ! Чё достанешь — хз.
Object obj = list.get(0); // Только так, с прищуром, как Object.
А чтобы не запоминать эту хуйню, умные дядьки придумали мнемонику PECS (Producer-Extends, Consumer-Super). Запомни раз и нахуй:
- Если параметр ПРОИЗВОДИТ (producer) тебе значения (
T), используйextends. Читать можно. - Если параметр ПОТРЕБЛЯЕТ (consumer) твои значения (
T), используйsuper. Писать можно.
Вот и вся магия, блядь. Сначала мозг ебётся, а потом привыкаешь. Главное — не пытайся протолкнуть Double в список для Integer, а то комплятор тебе такую пиздюлину впишет, что мало не покажется.