Generic
Generic wildcards
- unbound -
List<?>. Фактически, любой тип данных кроме примитивов. Используется в том случае, если надо разрешить использовать любой тип данных:
1 | List<String> list = ...; |
- upper-bound -
List<? extends Number>. Любой наследник, а так же сам целевой тип. Для данного типа (де-факто) запрещена любая вставка (кромеnull) - для вставки используетсяList<? super Number>. При этом, первичная вставка допускается:
1 | Map<String, ? extends String> map = new HashMap() {{ |
Перелистывание (например, через for-each) возможно с определением целевого типа:
1 | for(String val : map.values()) { ... } |
- lower-bound -
List<? super Number>. Любой класс, от которого целевой наследуется, или сам целевой. Данный вид wildcard не может быть использован в методе. Вставка доступна, а получение - доступно только с принудительным преобразованием:
1 | List<? super Number> nums = new ArrayList<>() {{ |
Ковариантность
Если объект, или массив объектов, или какой-то типа данных может быть применен к другому (родительскому) типу, то это называется ковариантность:
1 | String[] a = {"a", "b", "c"}; |
PECS (Producer Extends Consumer Super)
Был описан выше, но для повторения:
- если
<? extends ...>- это только чтение (Producer); для коллекций он ничего в себя не принимает, только отдает; - если
<? super ...>- это только хранение (Consumer); в случае коллекций он только принимает и ничего не отдает.
Важно! В случае unbound, это де-факто <? extends Object>, а значит допускается только чтение! Если предполагается и читать и записывать, лучше не использовать wildcards.
Переменные типа
1 | public <T extends Comparable<T>> T method(...) { ... } |
В примере выше присутствует конструкция, называемая, recursive bound - т.е. когда при определении Generic где-то в нем используется целевой класс - т.е. в нашем случае T используется в Comparable<T>. Это действие ограничено только правилами наследования. Так же, в примере выше нельзя использовать <T super ...>.
Multiple bound
Множественные ограничения, которые накладывают дополнительные ограничения на тип:
1 | public <T extends String & Comparable<? super T>> T method(...) {...} |
Важно! Второй и последующими границами могут быть только интерфейсы. Например:
1 | public <T extends Number & Comparable<T> & CharSequence>void test(T item) |
Type erasure
В действительности, никаких типизаций не существует в скомпилированном коде - компилятор перед использованием Generic типа выполняет принудительный type-cast:
1 | List<String> list = ...; |
В таком случае могут возникать непредвиденные ситуации:
1 | public class MyClass implements Comparable<MyClass> { |
В примере выше, компилятор из первого метода создает фоновый метод compareTo(Object), который мы пытаемся создать. И поскольку фоновый и созданный имеют одинаковую сигнатуру, они будут друг с другом конфликтовать.
Причем, если порядок определения методов будет соответствовать указанному выше, ошибка будет при определении класса с указанием дубликата метода compareTo. Если дефиницию методов поменять местами, то ошибка будет на первом методе (с сигнатурой Object), сигнализирующая о дублировании метода compareTo.
Игра на понижение
Для коллекций Java не дает понижать типы:
1 | List<Object> objectList = null; |
и даже указание generics на понижение не поможет:
1 | objectList = (List<Object>) stringList; // ошибка компиляции |
тогда можно просто отказаться от него:
1 | objectList = (List) stringList; |
Это плохая практика и валидация кода может не пройти, но компилятор такое пропустит. Если бы generic объект был неконвертируемым, скажем, objectList был бы объявлен как List<Number>, то при чтении из objectList (например, итерации как Number) было бы выброшено ClassCastException.