Дженерики

Дженерики #

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)
Присвоитьтолько TypeType и наследников TypeType и родителей Type
ЧитатьType и родителей TypeType и родителей TypeObject
Добавлять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>