Питання 12 · Розділ 20

У чому переваги використання дженериків

Structured Java interview answer with junior, middle, and senior-level explanation.

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Дженерики дають три головні переваги:

  1. Безпека типів — помилки ловляться на етапі компіляції, а не в runtime
  2. Не потрібен cast — код чистіший і безпечніший
  3. Перевикористання коду — один клас працює з різними типами
// ❌ Без дженериків (до Java 5)
List list = new ArrayList();
list.add("Hello");
list.add(42);  // компілятор не лається, але це баг!
String s = (String) list.get(1);  // ClassCastException в runtime!

// ✅ З дженериками
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(42);  // помилка компіляції — тип захищений!
String s = list.get(0);  // без cast!

🟡 Middle Level

Детальні переваги

1. Compile-time type safety:

// Помилка виявляється до запуску
List<User> users = new ArrayList<>();
users.add(new User());
// users.add("string");  // compilation error — одразу видно проблему

2. Elimination of casts:

// Без дженериків
Map map = new HashMap();
map.put("key", "value");
String value = (String) map.get("key");  // cast потрібен

// З дженериками
Map<String, String> map = new HashMap<>();
map.put("key", "value");
String value = map.get("key");  // без cast

3. Generic алгоритми:

// Один метод для будь-якого типу
public static <T> void swap(List<T> list, int i, int j) {
    T temp = list.get(i);
    list.set(i, list.get(j));
    list.set(j, temp);
}

swap(users, 0, 1);       // T = User
swap(strings, 0, 1);     // T = String
swap(integers, 0, 1);    // T = Integer

4. Спеціалізація через bounds:

// Працюємо тільки з Comparable типами
public static <T extends Comparable<T>> T max(List<T> list) {
    return list.stream().max(Comparable::compareTo).orElse(null);
}

max(List.of(3, 1, 4, 1, 5));  // 5
max(List.of("c", "a", "b"));  // "c"

Типові помилки

  1. Ігнорування warning: ```java // ⚠️ Unchecked cast warning List list = (List) someRawList;

// Краще — уникати raw types


2. **Змішування generic та raw:**
```java
List<String> typed = new ArrayList<>();
List raw = typed;  // raw type — втрачається безпека
raw.add(42);       // компілятор не лається

🔴 Senior Level

Internal Implementation

Type Erasure переваги:

1. Бінарна сумісність — Java 5 код працює з Java 1.4 бібліотеками
2. Zero runtime overhead — жодних додаткових перевірок
3. Немає додаткових даних в пам'яті

Bridge methods:

public class Node<T extends Comparable<T>> {
    private T data;
    public T getData() { return data; }
}

public class StringNode extends Node<String> {
    @Override
    public String getData() { return super.getData(); }
}

// Після erasure компілятор створює bridge method:
// public Object getData() { return this.getData(); } // викликає String getData()
// Це bridge method — зберігає поліморфізм після type erasure.

Архітектурні Trade-offs

Дженерики vs альтернативи:

Підхід Плюси Мінуси
Дженерики Type safety, перевикористання Type erasure обмеження
Object Простота Cast, runtime помилки
Кодогенерація Повна типізація Більше коду

Edge Cases

1. Generic varargs:

// ⚠️ Heap pollution warning
@SafeVarargs
public static <T> void printAll(List<T>... lists) {
    for (List<T> list : lists) {
        System.out.println(list);
    }
}

2. Generic exception:

// ❌ Не можна параметризувати Throwable
public class GenericException<T> extends Exception {}  // error

// ✅ Generic метод може кидати винятки
public static <T extends Exception> void rethrow(Exception e) throws T {
    throw (T) e;
}

Продуктивність

Дженерики:
- Runtime: Zero overhead (type erasure)
- Memory: Немає додаткових даних
- Cast: Неявні cast (negligible)

З дженериками vs без:
- Compile time: трохи довше (перевірка типів)
- Runtime: однаково
- Безпека: значно вища

Production Experience

Type-safe Builder:

public class QueryBuilder<T> {
    private final Class<T> resultType;
    private String sql;
    private final Map<String, Object> params = new HashMap<>();

    public QueryBuilder(Class<T> resultType) {
        this.resultType = resultType;
    }

    public QueryBuilder<T> sql(String sql) {
        this.sql = sql;
        return this;
    }

    public QueryBuilder<T> param(String name, Object value) {
        params.put(name, value);
        return this;
    }

    public List<T> execute() {
        // type-safe execution
        return jdbcTemplate.query(sql, params, resultType);
    }
}

// Використання
List<User> users = new QueryBuilder<>(User.class)
    .sql("SELECT * FROM users WHERE age > :age")
    .param("age", 18)
    .execute();

Best Practices

// ✅ Generic методи для перевикористання
public static <T> Optional<T> first(List<T> list, Predicate<T> predicate) {
    return list.stream().filter(predicate).findFirst();
}

// ✅ Bounded types
public static <T extends Comparable<T>> void sort(List<T> list) { }

// ✅ Wildcards для flexibility (PECS)
public void process(List<? extends Number> numbers) { }

// ❌ Raw types
// ❌ Unchecked casts без необхідності
// ❌ Ігнорування compiler warnings

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Compile-time type safety — помилки типів ловляться до запуску програми
  • Elimination of casts — не потрібен явний cast при отриманні з колекції
  • Code reuse — один generic клас працює з будь-яким типом
  • Type erasure = zero runtime overhead, бінарна сумісність з Java 1.4
  • Bridge methods зберігають поліморфізм після type erasure
  • Generic алгоритми: swap, max, filter працюють з будь-яким типом

Часті уточнюючі запитання:

  • Яка головна перевага дженериків? — Type safety на компіляції + відсутність cast
  • Чи є runtime overhead? — Ні, type erasure = zero overhead в runtime
  • Що таке bridge methods? — Синтетичні методи для збереження поліморфізму після erasure
  • Чи можна generic exception? — Ні, class GenericException<T> extends Exception — помилка

Червоні прапорці (НЕ говорити):

  • ❌ “Дженерики сповільнюють програму” — Zero runtime overhead
  • ❌ “Raw types — це нормально в новому коді” — Raw types вимикають всі перевірки типів
  • ❌ “Дженерики створюють додаткові об’єкти” — Жодних додаткових алокацій
  • ❌ “Generic varargs завжди безпечні” — Heap pollution можливий, потрібна @SafeVarargs

Пов’язані теми:

  • [[11. Що таке дженерики (Generics) в Java]]
  • [[13. Що таке type erasure (стирання типів)]]
  • [[17. Що таке PECS (Producer Extends Consumer Super)]]
  • [[19. Що таке raw types і чому їх слід уникати]]