Дженерики Java: Разве бросок не может Перечислить <Подкласс> для Списка <Суперкласс>? [дубликат]

103
задан SiLent SoNG 14 July 2010 в 12:39
поделиться

9 ответов

То, что вы видите во втором случае - это ковариация массива. Это плохая вещь IMO, которая делает присваивания внутри массива небезопасными - они могут не сработать во время выполнения, несмотря на то, что во время компиляции все было в порядке.

В первом случае, представьте, что код did скомпилировался, и за ним последовало:

b1.add(new SomeOtherTree());
DataNode node = a1.get(0);

Что вы ожидаете произойти?

Вы можете сделать это:

List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;

... потому что тогда вы можете брать вещи только из b1, и они гарантированно будут совместимы с Tree. Вы не можете вызвать b1.add(...) именно потому, что компилятор не будет знать, безопасно это или нет.

Посмотрите этот раздел FAQ по Java Generics от Angelika Langer для получения дополнительной информации.

116
ответ дан 24 November 2019 в 04:20
поделиться

Ну, здесь я буду честен: ленивая реализация genericity.

Нет никаких семантических причин не разрешать ваш первый аффект.

Кстати, хотя я обожал шаблонизацию в C++, дженерики, вместе с таким глупым ограничением, которое мы имеем здесь, являются основной причиной, почему я отказался от Java.

-3
ответ дан 24 November 2019 в 04:20
поделиться

Краткое объяснение: было ошибкой разрешить это изначально для массивов.

Более длинное объяснение:

Предположим, что это разрешено:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // pretend this is allowed

Тогда не могу ли я перейти к следующему:

b1.add(new TreeThatIsntADataNode()); // Hey, b1 is a List<Tree>, so this is fine

for (DataNode dn : a1) {
  // Uh-oh!  There's stuff in a1 that isn't a DataNode!!
}

Идеальное решение разрешило бы нужный вам вид приведения при использовании варианта List, доступного только для чтения, но запретило бы его при использовании интерфейса (например, List), доступного для чтения и записи. Java не допускает такого рода обозначения дисперсии для параметров generics, (*) но даже если бы допускала, вы не смогли бы привести List к List, если A и B не идентичны.

(*) То есть не допускает этого при написании классов. Вы можете объявить свою переменную как тип List, и это нормально.

17
ответ дан 24 November 2019 в 04:20
поделиться

Когда массивы были разработаны (т.е. практически когда была разработана java), разработчики решили, что дисперсия будет полезна, и разрешили ее. Однако это решение часто критиковали, потому что оно позволяет делать следующее (предположим, что NotADataNode является другим подклассом Tree):

DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2;   // this is okay
b2[0] = new NotADataNode(); //compiles fine, causes runtime error

Поэтому, когда были разработаны дженерики, было решено, что общие структуры данных должны допускать только явную дисперсию. Т.е. вы не можете сделать List b1 = a1;, но можете сделать List b1 = a1;.

Однако если вы сделаете последнее, попытка использовать метод add или set (или любой другой метод, принимающий в качестве аргумента T) вызовет ошибку компиляции. Таким образом, невозможно заставить компилироваться эквивалент приведенной выше проблемы с массивом (без небезопасного приведения).

7
ответ дан 24 November 2019 в 04:20
поделиться

List не расширяет List , хотя DataNode расширяет Tree . Это потому, что после вашего кода вы можете выполнить b1.add (SomeTreeThatsNotADataNode), и это будет проблемой, поскольку тогда в a1 будет элемент, который также не является DataNode.

Вам нужно использовать подстановочный знак, чтобы добиться чего-то вроде этого

List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
b1.add(new Tree()); // compiler error, instead of runtime error

С другой стороны DataNode [] ДЕЙСТВИТЕЛЬНО расширяет Tree [] . В то время это казалось логичным, но вы можете сделать что-то вроде:

DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2; // this is okay
b2[0] = new Tree(); // this will cause ArrayStoreException since b2 is actually a DataNode[] and can't store a Tree

Вот почему, когда они добавили обобщения в Коллекции, они решили сделать это немного по-другому, чтобы предотвратить ошибки времени выполнения.

9
ответ дан 24 November 2019 в 04:20
поделиться

Краткий ответ: Список a1 не того же типа, что и список b2; В a1 вы можете поместить любой объектный тип, который имеет расширение DataNode. Таким образом, он может содержать другие типы, кроме Tree.

2
ответ дан 24 November 2019 в 04:20
поделиться

Это ответ из C#, но я думаю, что здесь это не имеет значения, так как причина та же.

"В частности, в отличие от типов массивов, построенные ссылочные типы не проявляют "ковариантных" преобразований. Это означает, что тип List не имеет преобразования (ни неявного, ни явного) к List, даже если B является производным от A. Аналогично, не существует преобразования от List к List".

Причина этого проста: если преобразование в List разрешено, то, очевидно, можно хранить в списке значения типа A. Но это нарушит инвариантность. Но это нарушит инвариант, согласно которому каждый объект в списке типа List всегда является значением типа B, иначе могут возникнуть непредвиденные сбои при присваивании в классах коллекций."

http://social.msdn.microsoft.com/forums/en-US/clr/thread/22e262ed-c3f8-40ed-baf3-2cbcc54a216e

1
ответ дан 24 November 2019 в 04:20
поделиться

DataNode может быть подтипом Tree, но List DataNode не является подтипом List Tree.

https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html

1
ответ дан 24 November 2019 в 04:20
поделиться

Это классическая проблема с универсальными шаблонами, реализованными со стиранием типа.

Предположим, что ваш первый пример действительно сработал. Тогда вы сможете сделать следующее:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // suppose this works
b1.add(new Tree());

Но поскольку b1 и a1 относятся к одному и тому же объекту, это означает, что a1 теперь относится к Список , содержащий как DataNode , так и Tree . Если вы попытаетесь получить этот последний элемент, вы получите исключение (не помню, какой именно).

0
ответ дан 24 November 2019 в 04:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: