Ответ
Generics (обобщения) — это механизм Java, позволяющий создавать типобезопасные классы, интерфейсы и методы с параметризованными типами. Тип указывается в угловых скобках: <T>.
Основные преимущества:
- Безопасность типов (Type Safety): Ошибки несоответствия типов обнаруживаются на этапе компиляции, а не во время выполнения.
- Устранение приведения типов (Casts): Код становится чище, без постоянных
(String) list.get(0). - Повторное использование кода: Один алгоритм может работать с разными типами данных.
Без Generics (старый стиль):
List list = new ArrayList(); // Raw type
list.add("hello");
String s = (String) list.get(0); // Требуется явное приведение
Integer i = (Integer) list.get(0); // ClassCastException во время РАБОТЫ!
С Generics:
List<String> list = new ArrayList<>(); // Тип указан
list.add("hello");
String s = list.get(0); // Приведение не нужно
// list.add(123); // ОШИБКА КОМПИЛЯЦИИ - нельзя добавить Integer в List<String>
Создание generic-класса:
public class Box<T> {
private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
}
// Использование
Box<String> stringBox = new Box<>();
stringBox.setContent("Text");
Box<Integer> intBox = new Box<>();
intBox.setContent(42);
Wildcards (подстановочные типы):
// 1. Unbounded Wildcard - любой тип
public void printList(List<?> list) {
for (Object elem : list) System.out.println(elem);
}
// 2. Upper Bounded Wildcard - T и его ПОДКЛАССЫ
public double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) sum += n.doubleValue();
return sum;
}
// Можно передать List<Integer>, List<Double>, но НЕЛЬЗЯ добавлять элементы!
// 3. Lower Bounded Wildcard - T и его НАДКЛАССЫ
public void addNumbers(List<? super Integer> list) {
list.add(1); // Можно добавлять Integer
list.add(2);
}
// Можно передать List<Integer>, List<Number>, List<Object>
Ограничения Generics (Type Erasure):
- Стирание типов: Информация о generic-типах удаляется во время компиляции и недоступна во время выполнения.
// НЕ РАБОТАЕТ во время выполнения: if (list instanceof List<String>) { ... } // Ошибка компиляции T instance = new T(); // Ошибка компиляции T[] array = new T[10]; // Ошибка компиляции - Решение для массивов: Использовать
List<T>вместо массивов или создавать массив через рефлексию (не рекомендуется). - Нельзя использовать примитивы: Только ссылочные типы. Используйте
List<Integer>, а неList<int>.
Best Practices:
- Всегда используйте Generics для коллекций и собственных generic-классов.
- Предпочитайте
List<String>raw typeList. - Используйте
varс Generics для сокращения кода:var list = new ArrayList<String>(); - Для создания generic-массивов используйте обходной путь:
@SuppressWarnings("unchecked") T[] array = (T[]) new Object[size]; - Помните о PECS (Producer Extends, Consumer Super):
- Если коллекция производит элементы (
Tвозвращается) — используйте? extends T. - Если коллекция потребляет элементы (
Tпринимается) — используйте? super T.
- Если коллекция производит элементы (
Ответ 18+ 🔞
Давай разберём эту штуку, которая всех новичков в ступор вгоняет, а потом оказывается, что жить без неё нельзя. Generics, обобщения, эти ваши <T> в угловых скобках.
Представь себе, ты — царь и бог на своей кухне. У тебя есть один волшебный контейнер, в который можно положить что угодно: и хлеб, и гвозди, и носки. Это старый стиль, List list = new ArrayList(). Кладёшь туда строку "hello", а когда достаёшь — охуеваешь, потому что это оказывается кирпич. И получаешь ClassCastException прямо в ебало во время работы программы. Пиздец и расстройство.
List list = new ArrayList(); // Это контейнер для всего, он же свалка
list.add("hello");
String s = (String) list.get(0); // Надо гадать и приводить тип
Integer i = (Integer) list.get(0); // Runtime: БАБАХ! ClassCastException, всё сломалось!
А теперь Generics. Это как если бы ты взял этот контейнер и наклеил на него ярлык: «ТОЛЬКО ДЛЯ СТРОК, СУКА». Или «ТОЛЬКО ЦИФРЫ». Компилятор — это такой дотошный охранник, который смотрит на ярлык и не пускает мимоходом засунутый в список кирпич. Ошибка видна сразу, когда пишешь код, а не когда программа уже у клиента на сервере ебнулась.
List<String> list = new ArrayList<>(); // Ярлык "String" наклеен
list.add("hello");
String s = list.get(0); // Всё чисто, никаких танцев с (String)
// list.add(123); // Компилятор: "Нихуя себе, дружок! Ты куда это integer в список для строк суёшь? Иди нахуй!" Ошибка компиляции.
Зачем это, блядь, нужно?
- Безопасность типов: Охранник-компилятор не пропустит левое. Ошибки — на этапе написания, а не в проде. Волнение ебать — ноль.
- Убрать эти ёбаные приведения: Заебался уже писать
(String) something. Теперь код чистый. - Один раз написал — для всех типов работает: Создал алгоритм для коробки
Box<T>, и она работает и для строк, и для чисел, и для твоих кастомных объектов.
Создаём свою коробку с дженериком:
public class Box<T> { // T — это типа заглушка. Можешь назвать как угодно: Type, E, Shmotka
private T content; // Здесь будет лежать что-то типа T
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
}
// Используем
Box<String> stringBox = new Box<>();
stringBox.setContent("Письмо с угрозами");
// stringBox.setContent(42); // Не прокатит! Компилятор заругается.
Box<Integer> intBox = new Box<>();
intBox.setContent(42); // А вот тут — красота.
А теперь дикие карты (Wildcards). Это когда ты не знаешь точно тип, но знаешь его «родословную».
-
<?>— «Мне похуй, что там» (Unbounded).public void printList(List<?> list) { for (Object elem : list) System.out.println(elem); // Всё приводится к Object } // Передать можно список чего угодно, но положить в него ничего нового нельзя. Только смотреть. -
<? extends Number>— «Дай мне Number или любого его сына/внука» (Upper Bounded).public double sumOfList(List<? extends Number> list) { double sum = 0.0; for (Number n : list) sum += n.doubleValue(); // У всех наследников Number есть doubleValue() return sum; } // Можно передать List<Integer>, List<Double>. Но, внимание, хуй ты добавишь в этот список что-то новое! Только читать. -
<? super Integer>— «Дай мне Integer или его папу/дедушку» (Lower Bounded).public void addNumbers(List<? super Integer> list) { list.add(1); // А вот тут — можно добавлять Integer! list.add(2); } // Можно передать List<Integer>, List<Number>, List<Object>. Потому что везде можно положить циферку.
Главная подстава (Type Erasure):
Java, чтоб её, на этапе компиляции стирает всю информацию о дженериках. Во время выполнения твой List<String> превращается просто в List. Поэтому некоторые вещи невозможны:
// НЕ РАБОТАЕТ, НЕ МЕЧТАЙ:
if (list instanceof List<String>) { ... } // Ошибка компиляции. Во время выполнения никаких String в типе нет.
T instance = new T(); // Ошибка. Какой T, блядь? Он же стёрся.
T[] array = new T[10]; // Ошибка. Массив создаётся в runtime, а T уже нет.
Решение? Для массивов часто используют List<T> (списки), а если уж очень надо массив — костыль с приведением и аннотацией, чтобы компилятор не ныл:
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[size]; // Делай так, если очень припёрло, но это небезопасно.
Золотое правило PECS (Producer Extends, Consumer Super): Запоминай, чтобы не ебать мозг себе и другим:
- PRODUCER (поставщик, читаешь из коллекции) → Используй
extends(? extends T). - CONSUMER (потребитель, пишешь в коллекцию) → Используй
super(? super T).
Итог: Дженерики — это не просто синтаксический сахар, это банда-анал безопасности твоего кода. Используй всегда. Пиши List<String>, а не просто List. И будет тебе счастье, а не внезапный ClassCastException посреди ночи на проде.