Я просто пытаюсь понять extends
ключевое слово в Дженериках Java.
List<? extends Animal>
средства мы можем наполнить любой объект в List
который ЯВЛЯЕТСЯ A Animal
затем не будет следующее также означать то же самое:
List<Animal>
Кто-то может помочь мне знать различие между вышеупомянутыми двумя? Мне extends
просто звучите избыточными здесь.
Спасибо!
List
является подтипом List расширяет Animal>
, но не подтип List
.
Почему List
не является подтипом List
? Рассмотрим следующий пример:
void mySub(List<Animal> myList) {
myList.add(new Cat());
}
Если бы вам разрешили передать List
этой функции, вы бы получили ошибку времени выполнения.
РЕДАКТИРОВАТЬ: Теперь, если мы используем List вместо этого расширяет Animal>
, произойдет следующее:
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
}
За этим стоит теория Ко- и контравариантности .
Я вижу, что вы уже приняли ответ, но я просто хотел бы добавить свое мнение по этому поводу, так как думаю, что могу здесь чем-то помочь.
Разница между List
и List extends Animal>
выглядит следующим образом.
Используя 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 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
Выше мы пытаемся преобразовать Животное в Козла. Это не работает, потому что что, если после этого мы попытаемся заставить это Животное «бить головой», как это сделал бы козел? Мы не обязательно знаем, что Животное может это делать.
Это не так. List
говорит, что значение, которое присваивается этой переменной, должно быть «типа» List
. Однако это не означает, что должны быть только объекты Animal
, могут быть и подклассы.
List<Number> l = new ArrayList<Number>();
l.add(4); // autoboxing to Integer
l.add(6.7); // autoboxing to Double
Вы используете список расширяет конструкцию Number>
, если вас интересует список, содержащий объекты Number
, но сам объект List не обязательно должен иметь тип List
, но может любой другой список подклассов (например, List
).
Это иногда используется для аргументов метода, чтобы сказать «Мне нужен список из чисел
, но меня не волнует, будет ли это просто List
, это также может быть Список
". Это позволит избежать некоторых странных понижений, если у вас есть список некоторых подклассов, но метод ожидает список базового класса.
public void doSomethingWith(List<Number> l) {
...
}
List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // not working
Это не работает так, как вы ожидаете List
, а не List
. Но если вы написали List extends Number>
вы можете передавать объекты List
, даже если они не являются объектами List
.
public void doSomethingWith(List<? extends Number> l) {
...
}
List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // works
Примечание: Все это не связано с наследованием объектов в самом списке. Вы по-прежнему можете добавлять объекты Double
и Integer
в список List
, с или без ? расширяет материал
.