Первый говорит, что это «какой-то тип, который является предком E»; второй говорит, что это «некоторый тип, который является подклассом E». (В обоих случаях с самой E все в порядке.)
Таким образом, конструктор использует ? extends E
форму , так что гарантирует , что , когда он извлекает значения из коллекции, все они будут E или некоторый подкласс (т.е. он совместит). drainTo
Метод пытается положить значения в коллекцию, так что коллекция должна иметь тип элемента E
или суперкласс .
В качестве примера предположим, что у вас есть иерархия классов, подобная этой:
Parent extends Object
Child extends Parent
и LinkedBlockingQueue<Parent>
. Вы можете создать эту передачу в, List<Child>
которая будет безопасно копировать все элементы, потому что каждый Child
является родителем. Вы не могли передать, List<Object>
потому что некоторые элементы могут быть несовместимы с Parent
.
Точно так же вы можете слить эту очередь в a, List<Object>
потому что каждый Parent
является Object
... но вы не можете слить ее в a, List<Child>
поскольку List<Child>
ожидается, что все ее элементы будут совместимы Child
.
? extends InputStream
или? super InputStream
тогда, вы можете использовать вInputStream
качестве аргумента.Причины этого основаны на том, как Java реализует дженерики.
Пример массива
С массивами вы можете сделать это (массивы ковариантны)
Но что произойдет, если вы попытаетесь это сделать?
Эта последняя строка прекрасно скомпилируется, но если вы запустите этот код, вы можете получить
ArrayStoreException
. Потому что вы пытаетесь поместить двойное число в массив целых чисел (независимо от того, к чему вы обращаетесь через ссылку на число).Это означает, что вы можете обмануть компилятор, но вы не можете обмануть систему типов времени выполнения. И это так, потому что массивы - это то, что мы называем типами reifiable . Это означает, что во время выполнения Java знает, что этот массив был фактически создан как массив целых чисел, к которым просто случается обращение через ссылку типа
Number[]
.Итак, как вы можете видеть, одна вещь - это фактический тип объекта, а другая вещь - это тип ссылки, которую вы используете для доступа к ней, верно?
Проблема с Java Generics
Теперь проблема с универсальными типами Java заключается в том, что информация о типах отбрасывается компилятором и недоступна во время выполнения. Этот процесс называется стиранием типа . Есть веская причина для реализации таких обобщений в Java, но это длинная история, и она связана, помимо прочего, с бинарной совместимостью с уже существующим кодом (см. Как мы получили обобщенные образцы, которые у нас есть) ).
Но важным моментом здесь является то, что, поскольку во время выполнения нет информации о типе, нет способа гарантировать, что мы не допустим загрязнения кучи.
Например,
Если компилятор Java не мешает вам сделать это, система типов времени выполнения также не может остановить вас, потому что во время выполнения нет никакого способа определить, что этот список должен быть списком только целых чисел. Среда выполнения Java позволит вам поместить все, что вы хотите в этот список, когда он должен содержать только целые числа, потому что, когда он был создан, он был объявлен как список целых чисел.
Таким образом, разработчики Java позаботились о том, чтобы вы не могли обмануть компилятор. Если вы не можете обмануть компилятор (как мы можем сделать с массивами), вы также не можете обмануть систему типов времени выполнения.
Таким образом, мы говорим, что универсальные типы не подлежат переопределению .
Очевидно, это помешало бы полиморфизму. Рассмотрим следующий пример:
Теперь вы можете использовать это так:
Но если вы попытаетесь реализовать тот же код с универсальными коллекциями, у вас ничего не получится:
Вы получите ошибку компилятора, если попытаетесь ...
Решение состоит в том, чтобы научиться использовать две мощные функции обобщений Java, известные как ковариация и контравариантность.
ковариации
С ковариацией вы можете читать элементы из структуры, но вы не можете ничего в нее записать. Все это действительные декларации.
И вы можете прочитать из
myNums
:Потому что вы можете быть уверены, что независимо от того, что содержится в фактическом списке, его можно преобразовать в число (ведь все, что расширяет число, является числом, верно?)
Однако вам не разрешено помещать что-либо в ковариантную структуру.
Это было бы недопустимо, потому что Java не может гарантировать, что является фактическим типом объекта в общей структуре. Это может быть что угодно, что расширяет Number, но компилятор не может быть уверен. Так что вы можете читать, но не писать.
контрвариация
С противоположностью вы можете сделать наоборот. Вы можете поместить вещи в общую структуру, но вы не можете читать из нее.
В этом случае фактическая природа объекта - это Список объектов, и с помощью контравариантности вы можете поместить в него числа, в основном потому, что все числа имеют Объект в качестве общего предка. Таким образом, все числа являются объектами, и поэтому это действительно.
Однако вы не можете безопасно читать что-либо из этой контравариантной структуры, предполагая, что вы получите число.
Как вы можете видеть, если бы компилятор позволил вам написать эту строку, вы получите ClassCastException во время выполнения.
Принцип получения / сдачи
Таким образом, используйте ковариацию, когда вы намереваетесь извлекать общие значения из структуры, используйте контравариантность, когда вы только намереваетесь поместить универсальные значения в структуру, и используйте точный универсальный тип, когда вы собираетесь делать оба.
Лучший пример, который у меня есть, - это следующее, которое копирует любые номера из одного списка в другой. Он получает только предметы из источника и ставит предметы только в цель.
Благодаря полномочиям ковариации и контравариантности это работает для случая, подобного этому:
источник
List<Object> myObjs = new List<Object();
(который пропускает закрытие>
для второгоObject
).super.methodName
. При использовании<? super E>
это означает «что-то вsuper
направлении», а не что-то вextends
направлении. Пример:Object
вsuper
направленииNumber
(поскольку это суперкласс) иInteger
вextends
направлении (поскольку он расширяетсяNumber
).<? extends E>
определяетE
как верхнюю границу: «Это можно привести кE
».<? super E>
определяетE
как нижнюю границу: «E
можно привести к этому».источник
Object
это класс низшего уровня, несмотря на то, что он является конечным суперклассом (и рисуется вертикально в UML или подобных деревьях наследования). Я никогда не был в состоянии отменить это, несмотря на эоны попыток.Я собираюсь попытаться ответить на это. Но чтобы получить действительно хороший ответ, вам следует обратиться к книге Джошуа Блоха «Эффективная Java» (2-е издание). Он описывает мнемонический PECS, который расшифровывается как «Продюсер продлевает, потребительский супер».
Идея состоит в том, что если ваш код потребляет общие значения от объекта, вам следует использовать extends. но если вы создаете новые значения для универсального типа, вы должны использовать super.
Так, например:
И
Но на самом деле вы должны проверить эту книгу: http://java.sun.com/docs/books/effective/
источник
<? super E>
средстваany object including E that is parent of E
<? extends E>
средстваany object including E that is child of E .
источник
Возможно, вы захотите поискать в Google термины contravariance (
<? super E>
) и covariance (<? extends E>
). Я обнаружил, что наиболее полезной вещью при понимании обобщений было для меня понять сигнатуру методаCollection.addAll
:Так же, как вы хотели бы иметь возможность добавить
String
кList<Object>
:Вы также должны иметь возможность добавить
List<String>
(или любую коллекциюString
) с помощьюaddAll
метода:Однако вы должны понимать, что a
List<Object>
и aList<String>
не эквивалентны, и последний не является подклассом первого. Что нужно, так это концепция параметра ковариантного типа - то есть<? extends T>
бит.Как только вы это сделаете, очень просто подумать о сценариях, в которых вы также хотите использовать контравариантность (проверьте
Comparable
интерфейс).источник
Перед ответом; Пожалуйста, будьте уверены, что
Пример:
Надеюсь, что это поможет вам понять подстановочный знак более четко.
источник
Подстановочный знак с верхней границей выглядит как «расширяет тип» и обозначает семейство всех типов, которые являются подтипами типа, включая тип «тип». Тип называется верхней границей.
Подстановочный знак с нижней границей выглядит как «супер тип» и обозначает семейство всех типов, которые являются супертипами типа, включая тип «тип». Тип называется нижней границей.
источник
У вас есть родительский класс и дочерний класс, унаследованные от родительского класса. Родительский класс унаследован от другого класса, называемого GrandParent Class. Поэтому порядок наследования - GrandParent> Parent> Child. Теперь, <? extends Parent> - Это принимает родительский класс или дочерний класс <? super Parent> - Это принимает родительский класс или класс GrandParent
источник