Дженерики: Список <? расширяется Животное> является тем же как Список <Животное>?

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

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

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

List<Animal>

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

Спасибо!

45
задан Matt the Big 6 May 2013 в 19:40
поделиться

3 ответа

List является подтипом List , но не подтип List .

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

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

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


РЕДАКТИРОВАТЬ: Теперь, если мы используем List , произойдет следующее:

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

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

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
}

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

73
ответ дан 26 November 2019 в 20:54
поделиться

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

Разница между List и List выглядит следующим образом.


Используя List , вы знаете, что у вас определенно есть список животных. Необязательно, чтобы все они были в точности «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 , вы делаете заявление о типе списка , с которым имеете дело.

Например:

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

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

43
ответ дан 26 November 2019 в 20:54
поделиться

Это не так. List говорит, что значение, которое присваивается этой переменной, должно быть «типа» List . Однако это не означает, что должны быть только объекты Animal , могут быть и подклассы.

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

Вы используете список , если вас интересует список, содержащий объекты Number , но сам объект List не обязательно должен иметь тип List , но может любой другой список подклассов (например, List ).

Это иногда используется для аргументов метода, чтобы сказать «Мне нужен список из чисел , но меня не волнует, будет ли это просто List , это также может быть Список ". Это позволит избежать некоторых странных понижений, если у вас есть список некоторых подклассов, но метод ожидает список базового класса.

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

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

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

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

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

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

17
ответ дан 26 November 2019 в 20:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: