У вас есть два различных типа полиморфизма, которые взаимодействуют здесь, запутанно.
Ключом к пониманию этого является то, что в дополнение к параметрическому полиморфизму (то есть, дженерики) у вас также есть полиморфизм подтипа , то есть классический объектно-ориентированный отношения "is-a".
В Java все объекты являются подтипами Object
. Таким образом, контейнер, который может содержать Object
значений, может содержать любое значение.
Если мы переписываем все общие границы как просто <Object>
, тогда код работает так же, и, очевидно, так:
List<Object> objectList = new ArrayList<>();
objectList.add("str1");
List<Object> numberList = objectList;
numberList.add(1);
objectList.add("str2");
for (int i = 0; i < objectList.size(); i++) {
System.out.println(objectList.get(i) + "");
}
В частности, objectList.get(i) + ""
оценивается как нечто, вызывающее objectList.get(i).toString()
, а поскольку toString()
является методом Object
, он будет работать независимо от типа объектов в objectList
.
Что не сработает, так это:
Number number = numberList.get(i); // error!
Это потому, что, несмотря на вводящее в заблуждение имя, numberList
не обязательно содержит только Number
объектов и может фактически не содержать никаких Number
объектов вообще!
Давайте пройдемся и посмотрим, почему это так.
Сначала мы создадим список объектов:
List<? super Object> objectList = new ArrayList<>();
Что означает этот тип? Тип List<? super Object>
означает «список объектов какого-либо типа, я не могу сказать вам, какой тип, но я знаю, что любой тип это либо Object
, либо супертип Object
». Мы уже знаем, что Object
является корнем иерархии подтипов, так что это фактически то же самое, что и List<Object>
: то есть этот объект может содержать только Object
объектов.
Но ... это не совсем верно. Список может содержать только Object
объектов, но объект Object
может быть любым! Фактические объекты в типе runtype могут быть любого типа, который является подтипом Object
(так, что угодно, кроме примитива), но, помещая их в этот список, вы теряете возможность сказать, какие объекты они представляют больше - они могут быть чем угодно. Это нормально для того, что делает остальная часть этой программы, потому что все, что ей нужно сделать, это вызвать toString()
для объектов, и это можно сделать, потому что они все расширяют Object
.
Теперь давайте посмотрим на объявление другой переменной:
List<? super Number> numberList = objectList;
Опять же, что означает тип List<? super Number>
? Важно то, что это означает «список объектов какого-то типа, я не могу сказать вам, какой это тип, но я знаю, что какой бы это ни был тип, это либо Number
, либо какой-то супертип Number
». Ну, с левой стороны у нас есть список "Number
или некоторый супертип Number
", а с правой стороны у нас есть список Object
- ясно, что Object
является супертипом Number
и так что этот список является списком Object
. Все проверки типов (и, вопреки моему первоначальному комментарию, без каких-либо предупреждений).
Таким образом, возникает вопрос: почему List<? super Number>
может содержать String
? Потому что List<? super Number>
может быть просто List<Object>
, а List<Object>
может содержать String
, потому что String
is-a Object
.