Дженерики: Список <? Расширяет Animal> так же, как List <Animal>? - PullRequest
43 голосов
/ 04 апреля 2010

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

List<? extends Animal> означает, что мы можем заполнить любой объект в List, который ЕСТЬ Animal

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

List<Animal>

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

Спасибо!

Ответы [ 3 ]

70 голосов
/ 04 апреля 2010

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

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

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

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


РЕДАКТИРОВАТЬ: Теперь, если мы используем 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
}

Теория, лежащая в основе этого, - Совместная и контравариантность .

41 голосов
/ 04 апреля 2010

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

Разница между 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's 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

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

16 голосов
/ 04 апреля 2010

Это не так. List<Animal> говорит, что значение, которое присваивается этой переменной, должно иметь «тип» 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 вещи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...