Дженерики

Дженерики #

1. Что такое дженерики? Для чего нужны? #

Дженерики - это параметризованные типы в Java

  • Что это? Позволяют создавать классы, интерфейсы и методы с типами, заданными как параметры
  • Зачем нужны? Позволяют использовать один класс для разных типов данных (List<String>List<Integer>)

Набор  рекомендаций от Oracle о том, когда какие обозначения лучше использовать в дженериках:

  • E - element, для элементов параметризованных коллекций;
  • K - key, для ключей map-структур;
  • V - value, для значений map-структур;
  • N - number, для чисел;
  • T - type, для обозначения типа параметра в произвольных классах;
  • S, U, V и так далее - применяются, когда в дженерик-классе несколько параметров.


2. Что такое сырые типы? #

Сырые типы - это обобщённые классы без указания типа (List без <>)


3. Что такое вайлдкард #

Вайлдкард (символ ?) используется для обозначения неопределённого типа в дженериках


4. Расскажите про принцип PECS #

Producer Extends, Consumer - Super:

  • если мы объявили wildcard с extends, то это producer. Он только «продюсирует», предоставляет элемент из контейнера, а сам ничего не принимает
  • если же мы объявили wildcard с super, то это consumer. Он только принимает, а предоставить ничего не может

Для совмещения обеих возможностей необходимо использовать конкретный тип, а не wildcard.

Альтернативным вариантом является применение паттерна wildcard capture. На примере ниже для реверсирования коллекции с wildcard мы создаем обобщенный метод rev. Он объявлен с переменной типа T. Этот метод принимает список типов T, и мы можем сделать сет. При вызове метода reverse(List<?> list) в качестве аргумента передается список каких-то объектов (например, строк или целых чисел). Если мы можем захватить тип этих объектов и присвоить его переменной типа X, то можем заключить, что T является X.

public static void reverse(List<?> list) {
  rev(list);
}

private static <T> void rev(List<T> list) {
        List<T> tmp = new ArrayList<>(list);
        for (int i = 0; i < list.size(); i++) {
    list.set(i, tmp.get(list.size()-i-1));
        }
}


5. Расскажите про класс Optional #

Optional - класс-обертка. Он помогает избежать ошибок, связанных с null

Что делает Optional.flatMap()? Optional.flatMap() используется для работы с вложенными объектами Optional, помогая избежать двойной обёртки, т.е. убирает вложенность: превращает  Optional<Optional<T>> в Optional<T>

Optional<String> getNestedValue(Optional<Optional<String>> nestedOpt) {
    return nestedOpt.flatMap(opt -> opt);
}

Использовать map, если функция возвращает нужный вам объект, или flatMap, если функция возвращает Optional объект


6. Что такое стирание типов (Type Erasure) в Java Generics #

JVM не хранит информацию о параметризованных типах в рантайме. Все операции с дженериками преобразуются в операции с “сырыми типами” (raw types) на этапе компиляции (стирание типов)

Что такое стирание типов? Стирание типов (Type Erasure) - процесс, при котором информация о дженериках удаляется во время компиляции. JVM видит только “сырой” тип

Generics. Можно ли получить тип в рантайме? В общем случае нет. В Java используется стирание типов (Type Erasure), что означает, что информация о параметризованных типах удаляется после компиляции. На этапе выполнения JVM работает только с “сырыми типами”

Исключение: Можно сохранить информацию о типах с помощью рефлексии или через использование классов-обёрток

Пример с 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>

Табличка терминов по теме

ТерминРасшифровка
Дженерик-типы (generic types)Дженерик-класс или дженерик-интерфейс с одним или несколькими параметрами в заголовке
Параметризованный тип (parameterized types)Вызов дженерик-типа. Для дженерик-типа List<E> параметризованным типом будет, например, List<Integer>
Параметр типа (type parameter)Используются при объявлении дженерик-типов. Для List<E> E - это параметр типа
Аргумент типа (type argument)Тип объекта, который может использоваться вместо параметра типа. Например, для List<Integer> Integer - это аргумент типа
WildcardОбозначается символом «?» - неизвестный тип
Ограниченный wildcard (bounded wildcard)Ограниченный сверху <? extends Citrus> снизу - <? super Orange>
Сырой тип (raw type)Имя дженерик-типа без аргументов типа. Для List<E> сырой тип - это List
Универсальный параметр<T>, <E> и т.д. в объявлении классов, интерфейсов, методов