Дженерики: Список<? 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
вещи.