Тема 9. Generics
Generics в Java это механизм параметризации типов: вы пишете один универсальный код и при этом сохраняете строгую типобезопасность на этапе компиляции.
Если коротко, generics решают две проблемы:
- Избавляют от небезопасных приведения типов (
cast) в runtime. - Делают API самодокументируемым: по сигнатуре сразу видно, с какими типами работает код.
Почему generics вообще появились
До generics часто использовали «сырой» тип (raw type):
List list = new ArrayList();
list.add("Alice");
list.add(100);
String name = (String) list.get(1); // ClassCastException в runtime
Компилятор не может защитить вас заранее. Ошибка всплывает поздно, иногда уже в продакшене.
С generics:
List<String> names = new ArrayList<>();
names.add("Alice");
// names.add(100); // ошибка компиляции
String first = names.get(0);
Базовый синтаксис
T,E,K,Vэто имена параметров типа (конвенция).List<String>значит: список, который хранит толькоString.Map<String, Integer>значит: ключString, значениеInteger.
Типовые обозначения:
T(Type) — произвольный тип;E(Element) — элемент коллекции;K/V(Key/Value) — карта;R(Result) — тип результата функции.
Пример 1. Обобщенный метод
public static <T> void printAll(List<T> items) {
for (T item : items) {
System.out.println(item);
}
}
Использование:
printAll(List.of("A", "B", "C"));
printAll(List.of(1, 2, 3));
Один метод работает с разными типами без потери проверок компилятора.
Пример 2. Обобщенный класс
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
Использование:
Box<String> textBox = new Box<>();
textBox.set("hello");
String text = textBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(42);
int number = intBox.get();
Пример 3. Ограничения extends и super
public static double sum(List<? extends Number> nums) {
double total = 0.0;
for (Number n : nums) {
total += n.doubleValue();
}
return total;
}
public static void addDefaults(List<? super Integer> out) {
out.add(10);
out.add(20);
}
Смысл:
? extends Number: безопасно читать какNumber, но добавлять нельзя.? super Integer: можно добавлятьInteger, читать приходится какObject.
PECS: главное правило wildcards
Producer Extends, Consumer Super:
- Если источник производит данные для чтения, используйте
extends. - Если приемник потребляет данные для записи, используйте
super.
Это ключ к правильным сигнатурам API.
Invariance: важное ограничение Java Generics
В Java List<Integer> не является подтипом List<Number>.
Нельзя:
List<Integer> ints = List.of(1, 2, 3);
// List<Number> nums = ints; // ошибка
Почему: иначе в nums можно было бы добавить Double, сломав List<Integer>.
Type Erasure (стирание типов)
Generics в Java реализованы через стирание типов:
- во время компиляции проверяются ограничения типов;
- в байткоде конкретные type-аргументы в основном стираются.
Практические последствия:
- Нельзя сделать
new T(). - Нельзя напрямую проверять
obj instanceof List<String>. - Нельзя создать массив параметризованного типа
new List<String>[10].
Ограниченные параметры типа (bounded type parameters)
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
Здесь T обязан поддерживать сравнение с таким же T.
Можно и несколько ограничений:
<T extends Number & Comparable<T>>
Сначала класс, затем интерфейсы.
Generics + Collections: как проектировать сигнатуры
Плохая сигнатура:
void copy(List<Object> dst, List<Object> src)
Ломает переиспользование.
Хорошая сигнатура:
public static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (T item : src) {
dst.add(item);
}
}
Работает гибко и безопасно.
Частые ошибки новичков
- Использование raw types (
List list) вместоList<T>. - Путаница между
extendsиsuper. - Попытка обойти компилятор через unchecked cast.
- Слишком сложные generic-сигнатуры без реальной пользы.
Практический чеклист
- Всегда параметризуйте коллекции типом.
- Для API на чтение используйте
extends. - Для API на запись используйте
super. - Не усложняйте generic-подписи без необходимости.
- Если сигнатура трудно читается, дайте типовым параметрам осмысленные имена и добавьте короткий Javadoc.
Что важно запомнить
- Generics это инструмент качества API и раннего обнаружения ошибок.
- Главная выгода: меньше runtime-сбоев и меньше ручных cast'ов.
- Хорошо спроектированные generic-сигнатуры делают код и безопаснее, и удобнее для команды.