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 = newArrayList<Dog>(); // ArrayList implements List List<Animal> animals = dogs; // Awooga awooga animals.add(newCat()); Dogdog= 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> - но не записи.
Я думаю, к тому, что упоминается в другихответах, следует добавить, что, хотя
List<Dog> не является-a List<Animal>в Java
также верно, что
Список собак -это-список животных на английском языке (в разумной интерпретации)
То, как работает интуиция OP - которая, конечно, полностью верна - это последнее предложение. Однако, если мы применим эту интуицию, мы получим язык, который не похож на Java в своей системе типов: предположим, наш язык действительно позволяет добавлять cat в наш список собак. Что бы это значило? Это означало бы, что список перестает быть списком собак и остается просто списком животных. И список млекопитающих, и список четвероногих.
Другими словами: A List<Dog> в Java не означает "список собак", по-английски это означает "список собак и ничего, кроме собак".
В более общем плане, интуиция OP подходит для языка, в котором операции над объектами могут изменять их тип, или, скорее, тип (ы) объекта является (динамической) функцией его значения.