Generics - набор свойств языка позволяющих определять и использовать обобщенные типы и методы.
Основная идея заключается в том, чтобы позволить типам (классам и интерфейсам) быть параметрами при определении классов, интерфейсов и методов.
Обобщенные типы или методы отличаются от обычных тем, что имеют типизированные параметры (T – параметр в котором могут быть разные объекты).
ArrayList<Integer> list = new ArrayList<Integer>();
Свойства Generics:
Ограничения:
В <> могут быть только ссылочные типы: можно: String, Integer, int[], нельзя: int, double.
Пусть у нас есть тип Foo, который является подтипом Bar, и еще G - наследник Коллекции.
То G<Foo> не является наследником G<Bar>.
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}
Примером использования обобщенных типов может служить Java Collection Framework.
Так, класс LinkedList<E> - типичный обобщенный тип. Он содержит параметр E, который представляет тип элементов, которые будут храниться в коллекции.
Создание объектов обобщенных типов происходит посредством замены параметризированных типов реальными типами данных.
Вместо того, чтобы просто использовать LinkedList, ничего не говоря о типе элемента в списке, предлагается использовать точное указание типа LinkedList<String>, LinkedList<Integer> и т.п.
Generics (обобщенные типы и методы) позволяют нам уйти от жесткого определения используемых типов.
С их помощью можно объявлять классы, интерфейсы и методы, где тип данных указан в виде параметра.
Возможность создавать универсальные алгоритмы и структуры данных.
Позволяет осуществлять проверку на правильность написания кода во время компиляции, а не в Runtime.
Компилятор стирает все дженерики. В Runtime дженериков практически нет. Компилятор использует кастование.
Хоть бы слово про стирание типов...
Сделали использование Java Collection Framework проще, удобнее и безопаснее.
Это когда при компиляции из обобщенных классов с параметрами генерируется код обычных сырых классов с расстановкой, где нужно, приведения типов, генерацией дополнительных методов и т.д.
Дженерики существуют только на этапе компиляции и только для компилятора.
Стираются вверх (т.е. если не указано extends, то стирается в Object)
очень классный видосикRaw type - это имя интерфейса без указания параметризованного типа
Сырые типы — это типы без указания "уточненения" в фигурных скобках.
Нужны чтобы поддерживать старый код (обратная совместимость).
Рекомендуется использовать хоть какие-то параметризованные типы.
Также Diamond синтаксис связан с понятием "Type Inference", или же выведение типов. Ведь компилятор, видя справа <> смотрит на левую часть, где расположено объявление типа переменной, в которую присваивается значение. И по этой части понимает, каким типом типизируется значение справа.
На самом деле, если в левой части указан дженерик, а справа не указан <>, компилятор сможет вывести тип. Однако это будет смешиванием нового стиля с дженериками и старого стиля без них - вы теряете безопасность (типобезопасность) типов (может добавляться какой угодно тип в список, а не то что надо. Когда будет доставаться – то может быть сюрприз)))
ArrayList <String> strings = new ArrayList<>(); // parameterized type
ArrayList arrayList = new ArrayList(); // raw type
arrayList = strings; // Ok
strings = arrayList; // Unchecked assignment (назначение)
arrayList.add(1); //unchecked call
Языковая конструкция внутри diamond-оператора, позволяющая сделать код более универсальным.
Решает проблему наследования типов в дженериках.
Может быть 3-х типов: инвариантность, upper и lower.
The Get and Put Principle или PECS (Producer Extends Consumer Super) Get and Put Principe
Из одного типа переменных можно только читать, в другой — только вписывать (исключением является возможность записать null для extends и прочитать Object для super).
<?>
Запись вида Collection<?> равносильна Collection<? extends Object> , а значит — коллекция может содержать объекты любого класса, так как все классы в JAVA наследуются от Object – поэтому подстановка называется неограниченной.
Ковариация
Если мы объявили wildcard с extends, то это producer. Он только «продюсирует», предоставляет элемент из контейнера, а сам ничего не принимает.
Контрвариация
Если же мы объявили wildcard с super — то это consumer. Он только принимает, а предоставить ничего не может.
Если метод имеет аргументы с параметризованным типом (например, Collection или Predicate), то в случае, если аргумент - производитель (producer), нужно использовать ? extends T, а если аргумент - потребитель (consumer), нужно использовать ? super T.
Eсли метод читает данные из аргумента, то этот аргумент - производитель, Метод передаёт данные в аргумент, то аргумент является потребителем. Важно заметить, что определяя производителя или потребителя, мы рассматриваем только данные типа T.
Если ни хрена не поняли - смотрите видосик
Тип | Что можно присвоить = | Что можно читать get() | Что можно добавлять add() |
---|---|---|---|
Инвариантный List<Type> |
Только List<Type> | Type и предки Type | Type и наследники Type |
Ковариантный List<? extends Type> |
List<Type> и List наследников Type | Type и предки Type | Ничего |
Контрвариантный List<? super Type> |
List<Type> и List предков Type | Object | Type и наследники Type |