Список < Собака > подкласс List < Animal & gt ;? Почему обобщения Java не являются неявно полиморфными?

Некоторые языки используют := в качестве оператора присваивания.

711
задан cellepo 20 November 2018 в 09:22
поделиться

5 ответов

Нет, 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?

Вдруг у вас появляется очень запутавшийся кот.

Теперь вы не можете добавить Кот в список , потому что вы не знаете, что это List . Вы можете получить значение и знать, что это будет Animal , но вы не можете добавлять произвольных животных. Обратное верно для List - в этом случае вы можете безопасно добавить к нему Animal , но вы ничего не знаете о том, что из него можно извлечь, потому что это может быть List < Объект> .

868
ответ дан 22 November 2019 в 21:31
поделиться

В дополнение к ответу 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 объекты). Кроме того, копирование всего списка может быть неэффективным. Однако это действительно решает проблему эквивалентности типа путем повреждения ссылочного равенства.

0
ответ дан 22 November 2019 в 21:31
поделиться

Причина, по которой List не является List , заключается в том, что, например, вы можете вставить Cat в Список , но не в Список ... вы можете использовать подстановочные знаки, чтобы сделать универсальные шаблоны более расширяемыми, где это возможно; например, чтение из List аналогично чтению из List , но не записи.

В Generics in the Java Language и в разделе Generics of the Java Tutorials есть очень хорошее, подробное объяснение того, почему некоторые вещи являются или не являются полиморфными или разрешено с дженериками.

44
ответ дан 22 November 2019 в 21:31
поделиться

Я бы сказал, что весь смысл Generics в том, что он не позволяет этого. Рассмотрим ситуацию с массивами, которые допускают такой тип ковариации:

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

Этот код компилируется нормально, но выбрасывает ошибку времени выполнения (java.lang.ArrayStoreException: java.lang.Boolean во второй строке). Это не является безопасным с точки зрения типов. Смысл генериков в том, чтобы добавить безопасность типов во время компиляции, иначе вы могли бы просто использовать обычный класс без генериков.

Теперь бывают случаи, когда вам нужно быть более гибким, и именно для этого предназначены ? super Class и ? extends Class. В первом случае вам нужно вставить данные в тип Collection (например), а во втором - прочитать из него, причем безопасным для типов образом. Но единственный способ делать и то, и другое одновременно - это иметь определенный тип.

32
ответ дан 22 November 2019 в 21:31
поделиться

То, что вы ищете, называется параметрами ковариантного типа. Это означает, что если один тип объекта можно заменить другим в методе (например, Animal можно заменить на Dog), то это же относится и к выражениям, использующим эти объекты (так, List можно заменить на List). Проблема в том, что ковариация небезопасна для изменяемых списков в целом. Предположим, у вас есть List, и он используется как List. Что произойдет, если вы попытаетесь добавить кошку в этот List, который на самом деле является List? Автоматическое разрешение параметрам типа быть ковариантными нарушает систему типов.

Было бы полезно добавить синтаксис, позволяющий указывать параметры типа как ковариантные, что позволяет избежать ? extends Foo в объявлениях методов, но это добавляет дополнительную сложность.

79
ответ дан 22 November 2019 в 21:31
поделиться
Другие вопросы по тегам:

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