Является ли List <Double> подтипом List <? extends Number> и почему?
Вот что я знаю:
-
Double
является подтипом Number
и List<Double>
не является подтипом List<Number>
.
-
List<Dog>
не является подтипом List<Animal>
, потому что вы можете добавить Cat
в List<Animal>
, но вы не можете сделать это с помощью List<Dog>
.
-
List<? extends Number>
означает, что этот список может хранить переменные типа Number и переменные подтипа Number. List<Double>
означает, что этот список может хранить переменные типа Double.
Пожалуйста, поправьте меня, если что-то выше неправильно, а затем Is List<Double>
подтип List<? extends Number>
и почему?
Ответы
Ответ 1
Есть несколько пунктов, которые также следует добавить
-
Во время выполнения List<Double>
, List<? extends Number>
, List<?>
и List<Object>
все идентичны. Общий параметр не компилируется вообще. Все волшебство с дженериками - это все время компиляции. Это также означает, что если у вас есть пустой List
, вы не знаете, что такое общий параметр!
-
Попытайтесь не учитывать общий параметр как "подтип", общий параметр действительно означает "класс использует общий параметр", поэтому в этом случае "список использует число". Хорошим примером этого является источник HashMap, если вы посмотрите на его внутренние работы, он фактически хранит массив Entry
, а записи все имеют ключи и значения, хранящиеся на них. Когда вы смотрите на более сложные виды использования дженериков, вы иногда видите такое использование.
В случае a List
общий параметр означает, что список сохраняет этот тип объекта, возможно, объект никогда не хранит объект типа общего параметра вообще! Вот так:
public class DummyIterator<O> implements Iterator<O>{
public boolean hasNext() {
return false;
}
public O next() {
return null;
}
}
-
Что означает List<? extends Number>
? Ну, это почти так же, как List<Number>
для большинства применений. Имейте в виду, что, говоря ?
, вы в значительной степени говорите: "Меня не волнует тип", который проявляется в этой ситуации:
List<Double> doubles = new ArrayList<Double>();
List<? extends Number> numbers = doubles;
numbers.add(new Double(1)); //COMPILE ERROR
Number num = numbers.get(0);
Поэтому мы не можем добавить a Double
в <? extends Number>
. Но для этого примера:
List<Double> doubles = new ArrayList<Double>();
List<Number> numbers = doubles; //COMPILE ERROR
numbers.add(new Integer(1));
Number num = numbers.get(0);
Вы не можете назначить List<Double>
List<Number>
, что имеет смысл, поскольку вы конкретно говорите ему, что в списках используются только типы номеров
-
Итак, где вы должны использовать ?
? ну, в любом месте, где бы вы ни говорили: "Меня не интересует общий параметр", например:
boolean equalListSizes(List<?> list1, List<?> list2) {
return list1.size() == list2.size();
}
Вы использовали бы формат ? extends Number
только там, где вы не изменяете объект, используя общий параметр. так, например:
Number firstItem(List<? extends Number> list1) {
return list1.get(0);
}
-
Вместо того, чтобы использовать форматы ?
и ? extends Number
, попробуйте вместо этого использовать generics для класса/метода, в большинстве случаев это делает ваш код более читабельным!:
<T extends Number> T firstItem(List<T> list1) {
return list1.get(0);
}
Класс:
class Animal{}
class Dog extends Animal{}
class AnimalHouse<A extends Animal> {
List<A> animalsInside = new ArrayList<A>();
void enterHouse(A animal){
animalsInside.add(A);
}
A leaveHouse() {
return animalsInside.remove(0);
}
}
AnimalHouse<Dog> ah = new AnimalHouse<Dog>();
ah.enterHouse(new Dog());
Dog rufus = ah.leaveHouse();
-
В качестве дополнительной информации о дженериках вы можете также параметризовать методы для возврата определенного класса. Хорошим примером этого является метод any() в junit и пустой коллекции списка:
Dog rufus = Matchers.<Dog>any();
List<Dog> dogs = Collections.<Dog>emptyList();
Этот синтаксис позволяет указать тип возвращаемого объекта. Иногда довольно полезно знать (делает некоторые литье излишним)!
Ответ 2
Все ваши товары верны.
-
Double
является подтипом Number
и List<Double>
не является подтипом List<Number>
.
-
List<Dog>
не является подтипом List<Animal>
, потому что вы можете добавить Cat
в List<Animal>
, но вы не можете сделать это с помощью List<Dog>
.
Это правильно. Дженерики не ковариантны (но массивы!). Вот некоторые последующие чтения: Почему массивы ковариантны, но generics являются инвариантными?
-
List<? extends Number>
означает, что этот список может хранить переменные типа Number
и переменные подтипа Number
. List<Double>
означает, что этот список может хранить переменные типа Double
.
Это верно, но существует важное различие между List<Number>
и List<? extends Number>
. Вы можете думать о List<? extends Number>
как о списке конкретного Number
-subtype (который является одним из List<Double>
, List<Integer>
, List<Long>
,...) и List<Number>
как список, который потенциально может содержат смесь Double
, Integer
,...
Что касается вашего последнего вопроса:
Является List<Double>
подтипом List<? extends Number>
...
Да, вы можете иметь, например,
List<Double> doubles = new ArrayList<>();
List<? extends Number> numbers = doubles;
... и почему?
Это просто способ определения подтипирования.
Что касается мотивации, предположим, что у вас есть метод, который принимает список чисел. Если вы укажете параметр типа List<Number>
, вы не сможете передать ему List<Double>
. (Ваш второй вопрос в вашем вопросе объясняет, почему!) Вместо этого вы можете позволить параметру иметь тип List<? extends Number>
. Поскольку List<Double>
является подтипом List<? extends Number>
, он будет работать.
Ответ 3
Во время выполнения List<T>
и List<U>
идентичны List
(1).
Однако это изменится с введением типов значений (ожидается, что это произойдет в выпуске JDK 9 или JDK 10 не раньше середины 2016 года). List<T>
больше не будет таким же, как List<U>
из-за многочисленных ограничений, объясненных здесь Брайаном Гетцем: http://cr.openjdk.java.net/~briangoetz/valhalla/specialization.html
(1) - T
и U
типы отличаются в предыдущих утверждениях
Ответ 4
Это помогло мне увидеть дженерики как ограничения или контракты, а не как типы с подтипами.
Поэтому переменная List<? extends Number> var
говорит: var
- это список неизвестного типа ?
, который ограничен подтипом Number.
List<Number> listN;
List<Double> listD;
List<? extends Number> listX;
...
Number n1 = ...;
Double d1 = ...;
...
listN.add(n1); // OK n1 is a Number
listN.add(d1); // OK d1 is a Double, which is a Number
listD.add(n1); // compile error, n1 is not a Double
listD.add(d1); // OK
listX.add(n1); // compile error, because the exact type of list is not known! (prevents putting a Dog in a Cat list)
listX.add(d1); // compile error, same cause
Итак, когда вы не можете даже поместить число в List<? extends Number>
, какова цель такого списка? Он позволяет работать со списками, для которых точный тип не имеет значения для задачи:
// instead of several exactly typed methods...
int count(List<Number> numberList) {...}
int count(List<Object> objectList) {...}
// ...etc. you can have one with a degree of freedom:
int count(List<?> anyList) {...} // don't need to know the exact type of list
// instead of this...
Number sum(List<Number> numberList) {...}
Number sum(List<Double> doubleList) {...}
Number sum(List<Integer> integerList){...}
// you can do this, with a little less freedom in the ?
Number sum(List<? extends Number> list) {
// the only thing we need to know about the list type is that it is some number type
...
Number ni = list.get(i);
...
}
Использование подстановочных знаков ? extends X
позволяет расслабить жесткие контракты до более слабых условий.
Используя параметр named type, вы можете установить ограничения на разрешенные типы между несколькими переменными:
// works for any type T of list, whatever T is
// T is the same in the argument and in the return
<T> T pickObject(List<T> list, int index) {
return list.get(index);
}
// works for any type T of list, if T is a Number type
// T is the same in the argument and in the return
<T extends Number> T pickNumber(List<T> list, int index) {
return list.get(index);
}
...
List<Number> list;
Number n = pickNumber(list);