Дженерики #
1. Что такое дженерики? Для чего нужны? #
Дженерики - параметризованные типы в Java
- Что это? Позволяют создавать классы, интерфейсы и методы с типами, заданными как параметры
- Зачем нужны? Позволяют использовать один класс для разных типов данных (
List<String>
,List<Integer>
)
Набор рекомендаций от Oracle о том, когда какие обозначения лучше использовать в дженериках:
- E — element, для элементов параметризованных коллекций;
- K — key, для ключей map-структур;
- V — value, для значений map-структур;
- N — number, для чисел;
- T — type, для обозначения типа параметра в произвольных классах;
- S, U, V и так далее — применяются, когда в дженерик-классе несколько параметров.
2. Что такое сырые типы? #
Сырые типы - это использование обобщенных классов без указания типов, т.е. изначально класс или интерфейс описали с использованием дженериков, но при создании объекта этого класса тип ему не передали.
List rawList = new ArrayList();
List<String> list = new ArrayList<>();
rawList = list; // OK
list = rawList; // WARN - компилятор не знает какие типы данных хранит rawList, поэтому выдаст предупреждение
3. Что такое вайлдкард #
Вайлдкарды - это символ ‘?’, который используется для обозначения неизвестного типа в дженериках.
4. Расскажите про принцип PECS #
- Producer. Используется для чтения (? extends T)
- Consumer. Используется для добавления (? super T)
- Оба действия. Использовать конкретный тип, а не wildcard
Инвариантный List<Type> | Ковариантный List<? extends Type> (Producer Extends) | Контрвариантный List<? super Type> (Consumer Super) | |
---|---|---|---|
Присвоить | только Type | Type и наследников Type | Type и родителей Type |
Читать | Type и родителей Type | Type и родителей Type | Object |
Добавлять | Type и наследники Type | Ничего | Type и наследников Type |
5. Расскажите про класс Optional #
Optional - Класс-обертка. Он помогает избежать ошибок, связанных с null
Что делает Optional.flatMap()
Optional.flatMap()
используется для работы с вложенными объектами Optional
, помогая избежать двойной обёртки.
Optional<String> getNestedValue(Optional<Optional<String>> nestedOpt) {
return nestedOpt.flatMap(opt -> opt);
}
- Убирает вложенность: из
Optional<Optional<T>>
превращает вOptional<T>
.
Используйте map
, если функция возвращает нужный вам объект, или flatMap
, если функция возвращает Optional
объект.
6. Как JVM работает с Generics #
- JVM не хранит информацию о параметризованных типах в рантайме.
- Компилятор стирает информацию о типе, заменяя все параметры без ограничений (unbounded) типом
Object
, а параметры с границами (bounded) — на эти границы.
Что такое стирание типов?
Стирание типов (Type Erasure) — процесс, при котором компилятор стирает информацию о типе, заменяя все параметры без ограничений (unbounded)
типом Object
, а параметры с границами (bounded) — на эти границы. Это делается для обеспечения обратной совместимости с кодом, написанным до появления дженериков в Java 5.
Стирание состоит из трех действий:
- Если параметры ограничены (bounded), вместо типа-параметра в местах использования подставляется верхняя граница, иначе
Object
. - В местах присвоения значения типа-параметра в переменную обычного типа компилятор автоматически добавляет приведение типов (casting).
- Для поддержки полиморфизма компилятор создает мостовые методы (bridge methods) в случае, если дженерик используются в наследовании.
Generics. Можно ли получить тип в рантайме?
Нет, в общем случае нельзя. В Java используется стирание типов (Type Erasure
), что означает, что информация о параметризованных типах удаляется после
компиляции.
Исключения:
- Дженерик с wildcard без ограничений, например List будет считаться List.
- Можно сохранить информацию о типах с помощью рефлексии или через использование классов-обёрток.
Пример с TypeToken
:
import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;
Type type = new TypeToken<List<String>>(){}.getType();
System.out.println(type); // java.util.List<java.lang.String>