Всем привет, сегодня решил немного написать про Generics, мы все используем их каждый день и при типичном выражении:
List<String> names = new ArrayList<>();
И кажется очевидным:
"У нас есть список строк, значит JVM знает, что внутри именно String".
Но это не совсем так.
В Java существует механизм, который называется стирание типов (Type Erasure).
Его смысл простой:
Информация о generic-типах существует только на этапе компиляции и удаляется перед выполнением программы.
Например:
List<String> strings = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
После компиляции для JVM это фактически:
List strings = new ArrayList();
List numbers = new ArrayList();
Для JVM оба объекта имеют один и тот же тип: ArrayList
Она не знает, что первый список был String, а второй — Integer.
Почему Java сделала именно так? - Главная причина — обратная совместимость. Generics появились только в Java 5.
До этого в коде было так:
List list = new ArrayList();
list.add("Java");
Если бы после появления Generics JVM начала хранить информацию о каждом параметре типа, старый код пришлось бы полностью менять.
Стирание типов позволило добавить Generics, не сломав существующую экосистему.
Но из-за этого появляются ограничения.
Например:
public class Box<T> {
public T create() {
return new T();
}
}
Такой код не скомпилируется. Потому что после стирания типов JVM не знает, что такое T.
Это может быть:
String
Integer
User
Или еще один пример:
if (obj instanceof List<String>) {
...
}
Возникает тоже ошибка, так как во время выполнения JVM видит только:
obj instanceof List
Она не знает про String.
Почему тогда это работает?
List<String> list = new ArrayList<>();
String value = list.get(0);
Ведь JVM не знает про String.
Проверку делает не JVM, а компилятор и он автоматически добавляет приведение: String value = (String) list.get(0);
Именно поэтому Generics в Java — это в первую очередь механизм безопасности типов на этапе компиляции, а не runtime-механизм.
Понимание стирания типов помогает разобраться:
- почему возникает ClassCastException;
- почему появляются bridge methods;
- почему нельзя создать массив new T[];
- почему возникают ошибки InvalidTypesException в некоторых фреймворках.