Дженерики: Список ‹? extends Animal ›такой же, как List ‹Animal›?

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

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

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

List<Animal>

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

Спасибо!


person peakit    schedule 04.04.2010    source источник
comment
Почему бы не использовать <T extendsAnimal> void foo(List<T> param)?   -  person Alex78191    schedule 07.10.2019


Ответы (3)


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
}

За этим стоит теория Ко- и контравариантность.

person Heinzi    schedule 04.04.2010
comment
В первом примере вы получите ошибку компилятора, а не ошибку времени выполнения, если передадите List ‹dog› в список ‹Animal› - person Don Li; 31.05.2017
comment
@DonLi: Конечно. Вот почему я использую второе условное выражение: Если вы разрешили ..., то есть в гипотетическом сценарии, когда компилятор считал List<Dog> подтипом List<Animal>. - person Heinzi; 31.05.2017

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

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

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]

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

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

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

person Cam    schedule 04.04.2010
comment
+1 за Это фактически не объявление типа объекта, который может содержать L. Это утверждение о том, на какие списки L может ссылаться. - person Heinzi; 05.04.2010
comment
и по той же причине вы не можете сделать L.add(new Goat()) потому что L может ссылаться на список собак, к которым вы не можете добавить Козу :) - person Tarun; 16.01.2013

Нет. 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 материалом или без него.

person Progman    schedule 04.04.2010
comment
хммм .. понял. Хорошее объяснение - person peakit; 04.04.2010