Вернёмся в 1998 год.
В java 1.2 появились интерфейсы Map, Set, List и основные реализации. В те времена они хранили только объекты:
List values = new ArrayList()Чтобы нормально работать, приходилось использовать явное приведение:
values.add("value");
String s = (String) values.get(0);
Главная проблема с коллекциями тех лет - type loss, потеря типа при компиляции и написании кода:❌ Можно запросто получить ClassCastException
❌ Надо помнить тип объектов
❌ Ошибки возникают только в рантайме
В java 5 появилась возможность указать тип через дженерики:
List<String> values = new ArrayList<String>();✅ Удобно пользоваться
values.add("p");
String v = values.get(0);
✅ Хорошо читается
✅ Компилятор проверяет типы
Как реализованы дженерики?
Примерим на себя должность архитектора java и прикинем, что можно сделать с ArrayList<T>:
1️⃣ Скомпилировать в класс с типом String
Все T заменяем на String:
void add(String str)В итоге создаём отдельный класс для каждого типа данных.
String get(int index)
2️⃣ Добавить в JVM параметризованный тип
<T> остаётся на уровне байт-кода и определяется для каждого объекта в рантайме.
3️⃣ Оставить на уровне JVM работу с объектами. Проверить все типы во время компиляции и привести один к другому там, где нужно.
Получаем один ArrayList.class и множество дополнительных конструкций по всему коду.
Для ArrayList<String> программист напишет:
String value = list.get(0)Компилятор преобразует это в
String value = (String) list.get(0);🤔Все варианты адекватные и имеют право на жизнь. Что же выбрать?
Почти в каждом IT офисе на стенах висят слоганы и ценности компании. Что-то про качество работы, развитие сотрудников, гибкость и довольных клиентов. Из этого получается классный корпоративный мерч.
Ценности нормального человека определяют стратегию и дают ориентир для решения сложных ситуаций. "Ценности" java явно нигде не определены, но направление работы задано чётко. Например, кроссплатформенность и слоган Write Once, Run Anywhere заставляют разработчиков JDK поддерживать фичи даже для древних процессоров.
Бинарная совместимость - ещё один ориентир java. Поэтому для дженериков используется третий вариант: компилятор добавляет необходимые приведения типов в код и стирает информацию о дженериках. И код на java 5 теперь совместим с предыдущими версиями.
Ответ на вопрос перед постом
В консоли напечатаются оба значения. У списка tmp тип не задан, поэтому компилятор не мешает добавлять строки и передавать объекты в метод println.
Если в println передать список intList.get(0), где тип указан явно, то мы получим ошибку компиляции.