Дженерики: Список<? extends Animal> - это то же самое, что List<Animal>?

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

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

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

List<Animal>

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

Спасибо!

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
}

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

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

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

Так что теперь следующее не действует:

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

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

Это не. 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> too ". Это позволяет избежать некоторых странных бросков вниз, если у вас есть список некоторых подклассов, но метод ожидает список базового класса.

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 вещи.

Другие вопросы по тегам