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

Чи можна створити масив дженерик-типу

Це порушує type safety — помилка не ловиться на компіляції.

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

🟢 Junior Level

Ні, напряму не можна. Java не дозволяє створювати масиви дженерик-типів через type erasure.

// ❌ Не можна
public class Box<T> {
    private T[] array = new T[10];  // compilation error: "generic array creation"
}

Чому: Масиви в Java знають свій тип в runtime, а дженерики втрачають тип після компіляції (type erasure). Ці два механізми несумісні.

Рішення:

// Рішення 1: Масив з Object з cast
public class Box<T> {
    private T[] array = (T[]) new Object[10];  // unchecked warning
}

// Рішення 2: ArrayList замість масиву
public class Box<T> {
    private List<T> list = new ArrayList<>();
}

// Рішення 3: Передача Class<T>
public class Box<T> {
    private T[] array;

    public Box(Class<T> type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }
}

🟡 Middle Level

Чому не можна?

Конфлікт моделей:

  • Масиви — reified (знають тип в runtime)
  • Дженерики — type erasure (тип втрачається)
// Якби можна було:
List<String>[] stringLists = new List<String>[10];
Object[] objects = stringLists;  // масиви covariant
objects[0] = List.of(42);        // List<Integer> — компілятор не лається!
String s = stringLists[0].get(0); // ClassCastException!

Це порушує type safety — помилка не ловиться на компіляції.

Робочі рішення

1. Unchecked cast:

public class GenericStack<T> {
    private T[] elements;
    private int size = 0;

    @SuppressWarnings("unchecked")
    public GenericStack(int capacity) {
        elements = (T[]) new Object[capacity];  // unchecked warning
    }

    public void push(T item) {
        elements[size++] = item;
    }

    public T pop() {
        return elements[--size];
    }
}

2. Reflection:

public class GenericArray<T> {
    private final T[] array;

    public GenericArray(Class<T> type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }

    public void set(int index, T value) {
        array[index] = value;
    }

    public T get(int index) {
        return array[index];
    }
}

// Використання
GenericArray<String> strings = new GenericArray<>(String.class, 10);
strings.set(0, "Hello");

3. Колекції замість масивів:

// Найкраще рішення в більшості випадків
public class Box<T> {
    private final List<T> items = new ArrayList<>();

    public void add(T item) { items.add(item); }
    public T get(int index) { return items.get(index); }
}

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

  1. Ignored unchecked warning: ```java // ⚠️ Warning приглушений, але проблема залишається T[] array = (T[]) new Object[10];

// Краще — документувати чому це безпечно


2. **Heap pollution:**
```java
// Небезпечно — можна покласти неправильний тип
Object[] raw = new Object[10];
String[] strings = (String[]) raw;  // ClassCastException!

🔴 Senior Level

Internal Implementation

Чому масиви reified:

JVM specification:
- Масиви зберігають тип компонента в class file
- Runtime перевірка при кожному доступі
- ArrayStoreException при неправильному типі

// Приклад
String[] arr = new String[10];
arr[0] = "Hello";     // OK
arr[1] = 42;          // ArrayStoreException в runtime!

Type erasure конфлікт:

// Якби generic arrays були дозволені:
List<String>[] lists = new List<String>[5];
Object[] obj = lists;  // covariant
obj[0] = new ArrayList<Integer>();  // OK для компілятора
String s = lists[0].get(0);         // ClassCastException!

// Масив не може перевірити тип елемента в runtime,
// бо List<String> і List<Integer> — один raw type після erasure

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

Підходи до масивів дженериків:

Підхід Плюси Мінуси
Object[] + cast Просто Unchecked warning
Reflection Type-safe Потрібен Class
Колекції Безпечно Більше overhead

Edge Cases

1. Generic varargs:

// Varargs — це масиви!
public static <T> List<T> asList(T... items) {
    return Arrays.asList(items);
}

// ⚠️ Heap pollution warning
asList("a", "b", "c");  // OK
// Але компілятор попереджає

// @SafeVarargs вирішує
@SafeVarargs
public static <T> List<T> safeAsList(T... items) {
    return Arrays.asList(items);
}

2. Nested generic arrays:

// Ще складніше — nested типи
public class Matrix<T> {
    // ❌ Не можна
    private T[][] matrix = new T[10][10];  // error

    // ✅ Через Reflection
    private T[][] matrix;

    public Matrix(Class<T> type, int rows, int cols) {
        matrix = (T[][]) Array.newInstance(type, rows, cols);
    }
}

3. ArrayList.toArray() проблема:

List<String> list = List.of("a", "b", "c");

// ❌ Не можна
String[] array = (String[]) list.toArray();  // ClassCastException
// toArray() повертає Object[]

// ✅ Правильно
String[] array = list.toArray(new String[0]);
// Або Java 11+
String[] array = list.toArray(String[]::new);

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

Масив vs ArrayList:
Операція          | Array        | ArrayList
------------------|--------------|----------
Access            | 1 ns         | 1 ns
Add               | N/A (fixed)  | 1-10 ns (resize)
Memory            | Мінімум      + overhead
Type check        | Runtime      | Compile time

Generic array creation:
- Object[] + cast: Zero overhead
- Reflection: ~5-10 ns на створення
- Колекції: +10-20% пам'яті

Production Experience

Java collections internal:

// ArrayList internal — використовує Object[] + cast
public class ArrayList<E> {
    private transient Object[] elementData;  // NOT E[]

    @SuppressWarnings("unchecked")
    public E get(int index) {
        return (E) elementData[index];  // unchecked cast
    }
}

// HashMap — аналогічно
transient Node<K,V>[] table;  // array of raw Node

// Це безпечно, бо ArrayList контролює всі записи internal
// і не допускає елементи неправильного типу.

Safe generic array:

public class SafeGenericArray<T> {
    private final Object[] array;
    private final Class<T> type;

    public SafeGenericArray(Class<T> type, int size) {
        this.type = type;
        this.array = new Object[size];
    }

    public void set(int index, T value) {
        if (!type.isInstance(value)) {
            throw new IllegalArgumentException();
        }
        array[index] = value;
    }

    @SuppressWarnings("unchecked")
    public T get(int index) {
        return (T) array[index];
    }
}

Best Practices

// ✅ Використовуйте колекції замість масивів
List<T> list = new ArrayList<>();

// ✅ Якщо потрібен масив — Object[] + cast
@SuppressWarnings("unchecked")
private T[] array = (T[]) new Object[size];

// ✅ @SafeVarargs для varargs методів
@SafeVarargs
public static <T> T[] merge(T[] a1, T[] a2) { }

// ✅ Reflection коли потрібен type-safe масив
T[] array = (T[]) Array.newInstance(type, size);

// ❌ new T[]
// ❌ Ігнорування unchecked warnings без причини
// ❌ Heap pollution — передача generic масиву назовні

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

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

  • new T[] — compilation error: “generic array creation”
  • Причина: масиви reified (знають тип в runtime), дженерики — type erasure (тип втрачений)
  • Рішення 1: Object[] + unchecked cast (T[]) new Object[size]
  • Рішення 2: Reflection — Array.newInstance(type, size) з передачею Class<T>
  • Рішення 3: Використовувати ArrayList<T> замість масиву
  • @SafeVarargs приглушує warnings для varargs методів
  • ArrayList internal використовує Object[] + unchecked cast

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

  • Чому не можна створити generic масив? — Конфлікт reified масивів та type erasure дженериків
  • Як ArrayList зберігає елементи? — Object[] elementData + unchecked cast при get()
  • Що таке heap pollution? — Коли generic колекція містить елементи неправильного типу
  • Чи безпечний unchecked cast в ArrayList? — Так, ArrayList контролює всі записи internal

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

  • ❌ “Масив дженериків можна створити через unchecked cast без проблем” — Heap pollution можливий
  • ❌ “Generic масиви працюють в Java” — Заборонені на рівні компілятора
  • ❌ “ArrayList використовує T[] всередині” — ArrayList використовує Object[], не T[]
  • ❌ “Heap pollution — це помилка JVM” — Це compile-time warning, проблема type safety

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

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