Дженерики и массивы не дружат
Массивы reifiable (знают свой тип элемента в рантайме) и ковариантны; дженерики стёрты и инвариантны. Эти модели несовместимы, поэтому нельзя создать массив параметризованного типа: new List<String>[10] не компилируется, как и new T[n] внутри дженерик-кода.
Запрет не каприз: будь это разрешено, ковариантность массивов плюс стирание дали бы heap pollution — массив принял бы элемент неверного типа без ArrayStoreException (тип-то стёрт), и ClassCastException вылез бы позже в неожиданном месте.
Как обойти
Предпочтительно — использовать List<T> вместо массива дженериков: это и есть штатное решение. Если массив действительно нужен (например, в реализации коллекции), его создают через рефлексию Array.newInstance(componentType, n) с Class-токеном и приводят, локально подавляя предупреждение.
@SuppressWarnings("unchecked") ставят максимально узко (на переменную/выражение, а не на метод) и только когда вручную доказана типобезопасность — это осознанное «беру ответственность», а не способ заглушить варнинги.
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(componentType, n); // componentType: Class<T>
Heap pollution и varargs
Heap pollution — ситуация, когда переменная параметризованного типа ссылается на объект не того типа; компилятор предупреждает unchecked-варнингом. Частый источник — generic varargs: T... под капотом создаёт массив T[], а массив дженериков, как мы выяснили, небезопасен.
Если ваш generic-varargs-метод только ЧИТАЕТ элементы и не хранит/не публикует сам массив наружу, он безопасен — это помечают аннотацией @SafeVarargs, которая убирает предупреждение у вызывающих. Ставить её на небезопасный метод (который кладёт в массив или возвращает его) — ошибка.
Bridge-методы
Bridge-метод — синтетический метод, который компилятор генерирует, чтобы полиморфизм продолжал работать после стирания. Когда вы реализуете обобщённый интерфейс конкретным типом, стёртая сигнатура метода надкласса (например, compare(Object,Object)) расходится с вашей (compare(String,String)). Чтобы переопределение действительно срабатывало через ссылку на интерфейс, компилятор добавляет мост compare(Object,Object), который кастит и вызывает вашу версию.
Видеть их полезно: bridge-методы помечены isBridge()/isSynthetic() в рефлексии и иногда всплывают в стектрейсах — это не ваш код, а сгенерированный компилятором мостик.
class StrCmp implements Comparator<String> {
public int compare(String a, String b) { return a.length() - b.length(); }
// компилятор добавит синтетический bridge:
// public int compare(Object a, Object b) { return compare((String) a, (String) b); }
}
Reifiable и non-reifiable типы
Reifiable тип полностью представлен в рантайме: примитивы, обычные (не-параметризованные) классы, raw types и wildcard вида List<?>. Non-reifiable — параметризованные типы вроде List<String>, чья информация стёрта.
Отсюда правило: instanceof и создание массива работают только с reifiable-типами (obj instanceof List<?> можно, instanceof List<String> — нет), а приведение к non-reifiable типу даёт unchecked-предупреждение, потому что в рантайме его проверить невозможно.
Как отвечать на собеседовании
Объясните конфликт «массивы reifiable+ковариантны / дженерики стёрты+инвариантны» и почему запрещён new T[] (heap pollution). Расскажите про generic varargs и роль @SafeVarargs (метод только читает). Бонус-уровень — что такое bridge-методы (синтетический мост для полиморфизма после стирания) и reifiable vs non-reifiable (instanceof только с reifiable).