Подтвердить что ты не робот

Generics: List <? extends Animal> совпадает с List <Animal>?

Я просто пытаюсь понять ключевое слово extends в Java Generics.

List<? extends Animal> означает, что мы можем набить любой объект в List, который IS A Animal

то не будет означать следующее:

List<Animal>

Может кто-нибудь помочь мне узнать разницу между двумя этими двумя? Мне extends просто избыточно звучит здесь.

Спасибо!

4b9b3361

Ответ 1

List<Dog> является подтипом List<? extends Animal>, но не подтипом List<Animal>.

Почему List<Dog> не является подтипом List<Animal>? Рассмотрим следующий пример:

void mySub(List<Animal> myList) {
    myList.add(new Cat());
}

Если вам разрешено передать List<Dog> этой функции, вы получите ошибку времени выполнения.


EDIT: теперь, если вместо этого использовать List<? extends Animal>, произойдет следующее:

void mySub(List<? extends Animal> myList) {
    myList.add(new Cat());     // compile error here
    Animal a = myList.get(0);  // works fine 
}

Вы можете передать List<Dog> этой функции, но компилятор понимает, что добавление чего-то в список может вызвать у вас проблемы. Если вы используете super вместо extends (разрешая вам передать List<LifeForm>), это наоборот.

void mySub(List<? super Animal> myList) {
    myList.add(new Cat());     // works fine
    Animal a = myList.get(0);  // compile error here, since the list entry could be a Plant
}

Теория, лежащая в основе этого, Co- и Contravariance.

Ответ 2

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

Разница между List<Animal> и List<? extends Animal> заключается в следующем.


С List<Animal>, вы знаете, что у вас есть определенно список животных. Для всех не обязательно, чтобы они были точно "животными", они также могли быть производными типами. Например, если у вас есть список животных, имеет смысл, что пара может быть козлами, а некоторые из них - кошками и т.д. - правильно?

Например, это абсолютно верно:

List<Animal> aL= new List<Animal>();
aL.add(new Goat());
aL.add(new Cat());
Animal a = aL.peek();
a.walk(); // assuming walk is a method within Animal

Конечно, следующее недействительно:

aL.peek().meow(); // we can't do this, as it not guaranteed that aL.peek() will be a Cat

С List<? extends Animal> вы делаете выражение о типе списка, с которым имеете дело.

Например:

List<? extends Animal> L;

На самом деле это не объявление типа объекта L. Это утверждение о том, какие типы списков L могут ссылаться.

Например, мы могли бы сделать это:

L = aL; // remember aL is a List of Animals

Но теперь все компиляторы знают о L, что это Список [Animal или подтип Animal] s

Итак, теперь недопустимо следующее:

L.add(new Animal()); // throws a compiletime error

Потому что для всего, что мы знаем, L может ссылаться на список козлов, к которым мы не можем добавить Животное.

Вот почему:

List<Goat> gL = new List<Goat>(); // fine
gL.add(new Goat()); // fine
gL.add(new Animal()); // compiletime error

В приведенном выше примере мы пытаемся бросить животное как козу. Это не работает, потому что, если после этого мы попытались сделать, чтобы Animal сделал "headbutt", как коза? Мы не обязательно знаем, что животное может это сделать.

Ответ 3

Это не так. List<Animal> говорит, что значение, присвоенное этой переменной, должно быть "type" List<Animal>. Это, однако, не означает, что должны быть только объекты Animal, также могут быть подклассы.

List<Number> l = new ArrayList<Number>();
l.add(4); // autoboxing to Integer
l.add(6.7); // autoboxing to Double

Вы используете конструкцию List<? extends Number>, если вы заинтересованы в списке, который получил объекты Number, но сам объект List не должен иметь тип List<Number>, но может ли любой другой список подклассов (например, List<Integer>).

Это когда-нибудь используется для аргументов метода, чтобы сказать "Я хочу список Numbers, но мне все равно, просто ли это List<Number>, это может быть и List<Double>. Это позволит избежать некоторых странных прикладов, если у вас есть список некоторых подклассов, но метод ожидает список базового класса.

public void doSomethingWith(List<Number> l) {
    ...
}

List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // not working

Это не работает, поскольку вы ожидаете List<Number>, а не List<Double>. Но если вы написали List<? extends Number>, вы можете передать объекты List<Double>, даже если они не являются объектами List<Number>.

public void doSomethingWith(List<? extends Number> l) {
    ...
}

List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // works

Примечание.. Весь этот материал не связан с наследованием объектов в самом списке. Вы по-прежнему можете добавлять объекты Double и Integer в список List<Number>, с или без ? extends материала.