Ответ
Generics (обобщения) — механизм Java для создания классов, интерфейсов и методов, работающих с типами, заданными параметром. Обеспечивает безопасность типов на этапе компиляции и устраняет необходимость в приведении типов.
Основная цель:
- Type-safety: Компилятор проверяет корректность типов.
- Reusability: Один обобщенный код для разных типов.
- Устранение кастов: Избегание
ClassCastException.
Пример обобщенного класса:
public class Container<T> {
private T value;
public void setValue(T value) { this.value = value; }
public T getValue() { return value; }
}
// Использование
Container<String> stringContainer = new Container<>();
stringContainer.setValue("Hello");
String s = stringContainer.getValue(); // Без приведения типа
Как работают (Type Erasure): Во время компиляции информация о generic-типах стирается.
- Параметр типа
Tзаменяется на его верхнюю границу (по умолчаниюObject). - Генерируются необходимые приведения типов (casts).
- При компиляции
Container<String>становитсяContainer<Object>.
Ограничения (Bounds):
// T должен быть подтипом Number
public class NumericContainer<T extends Number> { ... }
// ? - wildcard (подстановочный знак)
public void processList(List<? extends Number> list) { ... } // Producer (чтение)
public void addToList(List<? super Integer> list) { ... } // Consumer (запись)
Практическое правило PECS:
- Producer Extends (
? extends T) — если структура является источником данных (читаем). - Consumer Super (
? super T) — если структура является приемником данных (записываем).
Ответ 18+ 🔞
Слушай, а вот эти ваши дженерики — это вообще пиздец какая удобная штука, если разобраться. Ну, типа, чтобы не писать один и тот же класс на каждый чих, под каждый тип, понимаешь? Один раз написал — и потом подсовываешь туда что угодно: строки, числа, свои объекты... Главное, чтобы компилятор не охуел от твоего креатива.
Вот смотри, в чём суть-то, блядь. Раньше-то как было? Хранил ты всё в Object, потому что он всему родитель. Засунул туда строку, достал — ага, надо кастовать: (String) obj. И если ты, мудак, случайно туда не строку сунешь, а, допустим, Integer, то в рантайме тебе пизда — ClassCastException прямо в ебало. А с дженериками компилятор, этот бдительный сукин сын, сразу тебя за руку ловит: «Куда, пидор? Ты же сюда только String договорился класть!».
Вот, например, контейнер для чего угодно. Пишем один раз и ебёмся с типом потом:
public class Контейнер<T> { // <--- Видишь эту Т? Это типа шаблон. Подставим потом что угодно.
private T содержимое;
public void засунуть(T штука) { this.содержимое = штука; }
public T вытащить() { return содержимое; } // И кастовать не надо, ёпта!
}
// А используем вот так:
Контейнер<String> дляСтрок = new Контейнер<>();
дляСтрок.засунуть("Привет, мир!");
String s = дляСтрок.вытащить(); // Всё, чисто, без плясок с бубном!
А как внутри-то работает, интересно? А хуй его знает! Шучу. Работает через стирание типов, ядрёна вошь. Компилятор, хитрая жопа, на этапе компиляции все эти <String>, <Integer> выкидывает к хуям и заменяет на Object (или на верхнюю границу, если она есть). А потом сам же, небось, в нужных местах невидимые касты подставляет. То есть в байт-коде твой Контейнер<String> и Контейнер<Integer> — это один и тот же, блядь, класс. Магия, да? Чистый фокус-покус для твоей же безопасности.
Но это ещё не всё! Можно же и ограничения ставить, чтобы не любой тип совали. Типа, «только числа, мудаки!».
public class ЧисловойКонтейнер<T extends Number> { ... } // Теперь T — это только Number или его наследник (Integer, Double).
ЧисловойКонтейнер<Integer> ок = new ЧисловойКонтейнер<>();
ЧисловойКонтейнер<String> неОк = new ЧисловойКонтейнер<>(); // Компилятор: «Иди нахуй!».
А ещё есть эти... как их... вайлдкарды, ?, подстановочные знаки. Вот где мозг можно ебнуть, если не понять простое правило PECS.
Запоминай, как «Отче наш»:
- Producer Extends (
? extends T) — если из коллекции ТОЛЬКО ЧИТАЕШЬ (она тебе производит данные).List<? extends Number>— можешь читать какNumber. - Consumer Super (
? super T) — если в коллекцию ТОЛЬКО ПИШЕШЬ (она твой потребитель).List<? super Integer>— можешь писатьIntegerи его наследников.
Проще говоря, если метод забирает объекты из коллекции — используй extends. Если метод суёт объекты в коллекцию — используй super. А если и то, и другое — тогда, блядь, не используй ? вообще, используй конкретный тип. И всё, и никакой хуйни!
Вот и весь сказ, блядь. Кажется сложным, но как вникнешь — жить без них уже не сможешь, как без рук. Главное — безопасность типов и никаких лишних кастов. Компилятор доволен, ты доволен. Красота, ёпта!