Вопрос-ответ

Is List a subclass of List? Why are Java generics not implicitly polymorphic?

Является ли List подклассом List? Почему дженерики Java неявно полиморфны?

Я немного запутался в том, как дженерики Java обрабатывают наследование / полиморфизм.

Предположим следующую иерархию -

Animal (родительский)

Dog - Cat (дочерние элементы)

Итак, предположим, у меня есть метод doSomething(List<Animal> animals). По всем правилам наследования и полиморфизма я бы предположил, что a List<Dog> является a List<Animal> и a List<Cat> является a List<Animal> - и поэтому любой из них может быть передан этому методу. Это не так. Если я хочу добиться такого поведения, я должен явно указать методу принять список любого подкласса Animal, сказав doSomething(List<? extends Animal> animals).

Я понимаю, что таково поведение Java. Мой вопрос в том, почему? Почему полиморфизм обычно неявный, но когда дело доходит до дженериков, он должен быть указан?

Переведено автоматически
Ответ 1

Нет, a List<Dog> это не a List<Animal>. Подумайте, что вы можете сделать с List<Animal> - вы можете добавить к нему любое животное ... включая кошку. Теперь, можете ли вы логически добавить кошку к выводку щенков? Абсолютно нет.

// 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?

Внезапно у вас появился очень сбитый с толку cat.

Теперь вы не можете добавить a Cat к a List<? extends Animal>, потому что вы не знаете, что это a List<Cat>. Вы можете получить значение и знать, что это будет Animal , но вы не можете добавить произвольных animals . Для List<? super Animal> верно обратное - в этом случае вы можете безопасно добавить к нему Animal , но вы ничего не знаете о том, что из него можно извлечь, потому что это может быть List<Object>.

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

Я думаю, к тому, что упоминается в других ответах, следует добавить, что, хотя


List<Dog> не является-a List<Animal> в Java


также верно, что


Список собак -это-список животных на английском языке (в разумной интерпретации)


То, как работает интуиция OP - которая, конечно, полностью верна - это последнее предложение. Однако, если мы применим эту интуицию, мы получим язык, который не похож на Java в своей системе типов: предположим, наш язык действительно позволяет добавлять cat в наш список собак. Что бы это значило? Это означало бы, что список перестает быть списком собак и остается просто списком животных. И список млекопитающих, и список четвероногих.

Другими словами: A List<Dog> в Java не означает "список собак", по-английски это означает "список собак и ничего, кроме собак".

В более общем плане, интуиция OP подходит для языка, в котором операции над объектами могут изменять их тип, или, скорее, тип (ы) объекта является (динамической) функцией его значения.

java generics inheritance