Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Особенности Дженериков в Java
Что такое дженерики
Дженерики — это механизм параметризации типов, который позволяет создавать классы, интерфейсы и методы, работающие с разными типами, но с сохранением type safety во время компиляции.
Основные особенности
1. Type Safety (Безопасность типов)
// ❌ Без дженериков (unsafe)
List list = new ArrayList();
list.add("Hello");
list.add(123);
String item = (String) list.get(1); // ClassCastException в runtime!
// ✅ С дженериками (safe)
List<String> list = new ArrayList<>();
list.add("Hello");
list.add(123); // ❌ Ошибка компиляции! Не compile
String item = list.get(0); // Безопасно, не нужен cast
2. Erasure (Стирание типов)
Это главная особенность и проблема дженериков в Java!
Вся информация о дженериках стирается во время компиляции. В runtime остаётся только raw тип:
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// В runtime:
stringList.getClass() == intList.getClass() // true!
// Оба это ArrayList.class
// Поэтому нельзя делать:
if (list instanceof List<String>) { } // ❌ Синтаксическая ошибка!
// А вот это работает:
if (list instanceof List<?>) { } // ✅ works
3. Следствие erasure: нет дженерик массивов
// ❌ Compile error!
List<String>[] array = new List<String>[10];
// Почему? Потому что List<String>[] в runtime это List[]
// и можно положить List<Integer>, что нарушит type safety
// ✅ Альтернатива: используй List
List<List<String>> list = new ArrayList<>();
4. Wildcard типы
// ? — неизвестный тип
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// ? extends T — верхняя граница (PECS)
public void processList(List<? extends Number> list) {
// можем читать как Number
Number num = list.get(0);
// но не можем добавлять (кроме null)
}
// ? super T — нижняя граница
public void fillList(List<? super Integer> list) {
// можем добавлять Integer
list.add(123); // ✅ Ok
// но не можем гарантировать тип при чтении
}
5. Bounded Type Parameters
// T должен быть подтипом Comparable
public class Container<T extends Comparable<T>> {
private T value;
public T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
}
// Множественные границы
public <T extends Number & Comparable<T>> void process(T value) {
// T должен быть Number И Comparable
}
Пример: Generic класс
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
// Использование:
Pair<String, Integer> pair = new Pair<>("age", 30);
String key = pair.getKey(); // No cast needed
Integer value = pair.getValue(); // No cast needed
Generic методы
public class Utils {
// Generic метод (независимо от класса)
public static <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
// Использование:
String first = Utils.getFirst(Arrays.asList("a", "b"));
Integer num = Utils.getFirst(Arrays.asList(1, 2, 3));
}
Ковариантность и Контравариантность (PECS)
public class Covariance {
// PECS = Producer Extends, Consumer Super
// Продюсер (читаем данные) — extends
public static void copy(List<? extends Number> src, List<Number> dst) {
for (Number num : src) {
dst.add(num);
}
}
// Консьюмер (пишем данные) — super
public static void fill(List<? super Integer> list) {
list.add(123);
}
}
Проблемы и Gotchas
1. Type erasure и отражение
public <T> T fromJson(String json) {
// ❌ Нельзя использовать T.class
// T стёрлся в runtime
Class<T> tClass = T.class; // ❌ Ошибка!
// ✅ Решение: передать Class
return gson.fromJson(json, String.class);
}
public <T> T fromJson(String json, Class<T> type) {
return gson.fromJson(json, type); // ✅ Ok
}
2. Raw типы (без параметризации)
// ⚠️ Raw type — опасен
List rawList = new ArrayList();
rawList.add("string");
rawList.add(123);
// Компилятор выдаст warning
// Это backwards compatibility с Java 1.4
3. Diamond оператор
// Java 7+: <> инферирует тип
List<String> list = new ArrayList<>(); // ✅ Good
// Java 6: нужно было указывать
List<String> list = new ArrayList<String>(); // verbose
Когда использовать дженерики
// ✅ Всегда:
- Коллекции: List<T>, Set<T>, Map<K, V>
- Обёрнутые типы: Optional<T>
- Контейнеры: Pair<K, V>, Container<T>
// ✅ Когда имеет смысл:
- API методы с типобезопасностью
- Сложные типы с параметризацией
// ❌ Не надо усложнять:
- Очень сложную иерархию дженериков
- Если простой Object подойдёт
Вывод
Главная особенность дженериков:
- Параметризация типов — типобезопасность во время компиляции
- Type Erasure — информация о типе стирается в runtime
- Wildcards — ? extends, ? super для ковариантности
- Bounded параметры — ограничение типов (T extends X)
- PECS — Producer Extends, Consumer Super правило
Дженерики делают код безопаснее и понятнее, но нужно помнить про erasure и его последствия.