Некоторые языки используют :=
в качестве оператора присваивания.
Нет, List
- это , а не a List
. Подумайте, что вы можете сделать с List
- вы можете добавить к нему любое животное ... включая кошку. Теперь, можно ли логично добавить кошку в помет щенков? Точно нет.
// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?
Вдруг у вас появляется очень запутавшийся кот.
Теперь вы не можете добавить Кот
в список расширяет Animal>
, потому что вы не знаете, что это List
. Вы можете получить значение и знать, что это будет Animal
, но вы не можете добавлять произвольных животных. Обратное верно для List super Animal>
- в этом случае вы можете безопасно добавить к нему Animal
, но вы ничего не знаете о том, что из него можно извлечь, потому что это может быть List < Объект>
.
В дополнение к ответу Jon Skeet, который использует этот пример кода:
// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?
На самом глубоком уровне, проблема здесь состоит в том, что dogs
и animals
совместно используют ссылку. Это означает, что один способ сделать эту работу состоял бы в том, чтобы скопировать весь список, который повредит ссылочное равенство:
// This code is fine
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog());
List<Animal> animals = new ArrayList<>(dogs); // Copy list
animals.add(new Cat());
Dog dog = dogs.get(0); // This is fine now, because it does not return the Cat
После вызова List<Animal> animals = new ArrayList<>(dogs);
, Вы не можете впоследствии непосредственно присвоиться animals
или к dogs
или к cats
:
// These are both illegal
dogs = animals;
cats = animals;
поэтому Вы не можете поместить неправильный подтип Animal
в список, потому что нет никакого неправильного подтипа - любой объект подтипа ? extends Animal
может быть добавлен к [1 111].
, Очевидно, это изменяет семантику, так как списки animals
и dogs
больше не совместно используются, таким образом добавление к одному списку не добавляет к другому (который является точно, что Вы хотите, для предотвращения проблемы, которая Cat
могла быть добавлена к списку, который, как только предполагается, содержит Dog
объекты). Кроме того, копирование всего списка может быть неэффективным. Однако это действительно решает проблему эквивалентности типа путем повреждения ссылочного равенства.
Причина, по которой List
не является List
, заключается в том, что, например, вы можете вставить Cat
в Список
, но не в Список
... вы можете использовать подстановочные знаки, чтобы сделать универсальные шаблоны более расширяемыми, где это возможно; например, чтение из List
аналогично чтению из List
, но не записи.
В Generics in the Java Language и в разделе Generics of the Java Tutorials есть очень хорошее, подробное объяснение того, почему некоторые вещи являются или не являются полиморфными или разрешено с дженериками.
Я бы сказал, что весь смысл Generics в том, что он не позволяет этого. Рассмотрим ситуацию с массивами, которые допускают такой тип ковариации:
Object[] objects = new String[10];
objects[0] = Boolean.FALSE;
Этот код компилируется нормально, но выбрасывает ошибку времени выполнения (java.lang.ArrayStoreException: java.lang.Boolean
во второй строке). Это не является безопасным с точки зрения типов. Смысл генериков в том, чтобы добавить безопасность типов во время компиляции, иначе вы могли бы просто использовать обычный класс без генериков.
Теперь бывают случаи, когда вам нужно быть более гибким, и именно для этого предназначены ? super Class
и ? extends Class
. В первом случае вам нужно вставить данные в тип Collection
(например), а во втором - прочитать из него, причем безопасным для типов образом. Но единственный способ делать и то, и другое одновременно - это иметь определенный тип.
То, что вы ищете, называется параметрами ковариантного типа. Это означает, что если один тип объекта можно заменить другим в методе (например, Animal
можно заменить на Dog
), то это же относится и к выражениям, использующим эти объекты (так, List
можно заменить на List
). Проблема в том, что ковариация небезопасна для изменяемых списков в целом. Предположим, у вас есть List
, и он используется как List
. Что произойдет, если вы попытаетесь добавить кошку в этот List
, который на самом деле является List
? Автоматическое разрешение параметрам типа быть ковариантными нарушает систему типов.
Было бы полезно добавить синтаксис, позволяющий указывать параметры типа как ковариантные, что позволяет избежать ? extends Foo
в объявлениях методов, но это добавляет дополнительную сложность.