Является ли 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 являются инвариантными?

  1. 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);