Является ли List‹Double› подтипом List‹? расширяет номер› и почему?

Вот что я знаю:

  1. Double является подтипом Number, а List<Double> не является подтипом List<Number>.
  2. List<Dog> не является подтипом List<Animal>, потому что вы можете добавить Cat к List<Animal>, но вы не можете сделать это с List<Dog>.
  3. List<? extends Number> означает, что в этом списке могут храниться переменные типа Number и переменные подтипа Number. List<Double> означает, что в этом списке могут храниться переменные типа Double.

Пожалуйста, поправьте меня, если что-то из вышеперечисленного не так, а затем является ли List<Double> подтипом List<? extends Number> и почему?


person kahofanfan    schedule 28.05.2015    source источник
comment
Пункт 3 верен, но вы не можете хранить оба Double и Integer в одном списке List<? extends Number>. И List<Double>, и List<Integer> равны List<? extends Number>, но List<? extends Number> не совпадает с List<Number>.   -  person aioobe    schedule 28.05.2015
comment
Я думаю, это ясно. Dog предположительно расширяет Animal.   -  person aioobe    schedule 28.05.2015


Ответы (4)


Есть несколько моментов, которые также следует добавить

  1. Во время выполнения List<Double>, List<? extends Number>, List<?> и List<Object> все идентичны. Общий параметр вообще не компилируется. Вся магия с дженериками — это развлечение во время компиляции. Это также означает, что если у вас есть пустой List, вы понятия не имеете, что такое универсальный параметр!

  2. Старайтесь не думать об универсальном параметре как о «подтипе», общий параметр на самом деле означает, что «класс использует общий параметр», поэтому в данном случае « список использует Число". Хорошим примером этого является HashMap. source, если вы посмотрите на его внутреннюю работу, он на самом деле хранит массив Entry, и все записи содержат ключи и значения, хранящиеся в них. Когда вы смотрите на более сложное использование дженериков, вы иногда видите такое использование.

    В ситуации с List общий параметр означает, что список хранит этот тип объекта, может случиться так, что объект вообще никогда не хранит объект типа универсального параметра! Как это:

    public class DummyIterator<O> implements Iterator<O>{
        public boolean hasNext() {
            return false;
        }
    
        public O next() {
            return null;
        }
    }
    
  3. Что на самом деле означает 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);
    

    Таким образом, мы не можем добавить 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>, что имеет смысл, поскольку вы специально говорите, что списки используют только числовые типы.

  4. Итак, где вы должны использовать ?? ну действительно везде, где вы могли бы сказать: «Меня не волнует общий параметр», например:

    boolean equalListSizes(List<?> list1, List<?> list2) {
      return list1.size() == list2.size();
    }
    

    Вы должны использовать формат типа ? extends Number только в том случае, если вы не изменяете объект с помощью универсального параметра. так например:

      Number firstItem(List<? extends Number> list1) {
        return list1.get(0);
      }
    
  5. Вместо использования форматов ? и ? extends Number попробуйте использовать дженерики для класса/метода, в большинстве случаев это также сделает ваш код более читабельным!:

    <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();
    
  6. В качестве бонуса в отношении дженериков вы также можете параметризовать методы для возврата определенного класса. Хорошим примером этого является метод any() в junit и коллекция пустых списков:

    Dog rufus = Matchers.<Dog>any();
    List<Dog> dogs = Collections.<Dog>emptyList();
    

    Этот синтаксис позволяет указать возвращаемый тип объекта. Иногда весьма полезно знать (делает некоторые приведения излишними)!

person FuzzyJulz    schedule 29.05.2015
comment
Не могли бы вы подробнее объяснить свой второй пункт? 1....поэтому в этом случае в списке используется число. В чем здесь смысл использования? Вы имеете в виду магазин? 2....в списке хранится этот тип объекта... и...может случиться так, что объект никогда не хранит объект типа универсального параметра... Это длинное предложение действительно слишком сложно для меня, чтобы понять (я не носитель языка) - person kahofanfan; 30.05.2015
comment
@kahofanfan: отредактировано, надеюсь, это проясняет для вас, что я имею в виду. - person FuzzyJulz; 30.05.2015
comment
Хорошие имена методов enterHouse() и leaveHouse() - person aliopi; 12.10.2015

Все ваши пункты верны.

  1. Double является подтипом Number, а List<Double> не является подтипом List<Number>.

  2. List<Dog> не является подтипом List<Animal>, потому что вы можете добавить Cat к List<Animal>, но вы не можете сделать это с List<Dog>.

Это правильно. Дженерики не ковариантны (но массивы!). Вот некоторые дополнительные материалы: Почему массивы ковариантны, а дженерики инвариантны?< /а>

  1. List<? extends Number> означает, что в этом списке могут храниться переменные типа Number и переменные подтипа Number. List<Double> означает, что в этом списке могут храниться переменные типа Double.

Это правда, но между List<Number> и List<? extends Number> есть важное различие. Вы можете думать о List<? extends Number> как о списке определенного Number-подтипа (то есть одного из 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>, это сработает.

person aioobe    schedule 28.05.2015
comment
Но List<? extends Number> может хранить переменные типа Int, а List<Double> — нет. И мы знаем, что подтип должен иметь все функции, которые выполняет его супертип. Так что я в замешательстве. - person kahofanfan; 28.05.2015
comment
Это все равно, что сказать, что Animal может лаять, а Cat нет. - person aioobe; 28.05.2015
comment
Еще одна вещь, которую следует учитывать: numbers.add(new Double(1.2)); и numbers.add(new Integer(1)); не будут компилироваться, но Number num = numbers.get(0); вернет что-то, что расширяет число. Как правило, старайтесь держаться подальше от <? extends X>, вам лучше использовать класс или использовать стиль <T>. например. public <T> void addToList(List<T> doubles, T value){ doubles.add(value); } - person FuzzyJulz; 28.05.2015
comment
Можете ли вы действительно считать, что List<Double> является подтипом List<? extends Number>, когда вы не можете перегрузить метод с обоими (не может иметь как sampleMethod(List<Double>), так и sampleMethod(List<? extends Number>) в одном классе), поскольку они эквивалентны во время выполнения (стирание, ну... стирается при компиляции)? - person Chop; 28.05.2015
comment
@Chop, я не думаю, что перегрузка метода связана с подтипом (по крайней мере, не в контексте этого вопроса). - person aioobe; 28.05.2015
comment
Еще один момент: вы не можете определить наследование при стирании с помощью instanceof. Я всегда понимал стирание как инструкции компилятора и разработчика о том, как использовать конкретный объект или метод, а не настоящую типизацию. Ваша точка зрения о том, что Animal лает, а Cat не может, неверна: если Cat extends Animal и Animal лает, то Cat тоже лает. Для меня вы можете только сказать, что списки удовлетворяют некоторым требованиям наследования, но, в конце концов, они одного типа с разными ограничениями. - person Chop; 28.05.2015
comment
@aioobe Вы можете иметь sampleMethod(Animal) и sampleMethod(Cat) в одном классе, потому что это разные типы. List — это всего лишь List, независимо от их стирания. - person Chop; 28.05.2015
comment
Я имел в виду, что Animal может лаять, если это Dog (извините, если это неясно). В таком случае, я думаю, мы оба согласны с тем, что Cat не может лаять :-) - person aioobe; 28.05.2015
comment
В то время как ОП спросил, почему вы можете добавлять целые числа к List<? extends Number> (Animal), а не к List<Double> (Cat). Я не вижу актуальности. (Хотя я могу показаться агрессивным, на самом деле я нахожу эту тему и дебаты довольно интересными). - person Chop; 28.05.2015
comment
А, теперь понятно. Хорошая точка зрения. Очень связано с предыдущим вопросом OP. - person aioobe; 28.05.2015
comment
@Chop У нас может быть подтип без наследования. Наследование — это только один из способов получить подтип в системе типов Java. List<Double> не наследуется от List<? extends Number>, но List<Double> является подтипом List<? extends Number>. - person Radiodef; 28.05.2015
comment
В Java подтипы в основном означают наследование. Здесь нет подтипов; есть только неявное преобразование из List<Double> в List<? extends Number>. - person cHao; 28.05.2015
comment
Но Лист‹? extends Number› может хранить переменные типа Int — нет, он может хранить List‹Integer›; это не то же самое. - person Random832; 28.05.2015
comment
@ Random832 Random832 Он также может хранить List<Number>, к которому вы можете добавить Integer экземпляров. - person Chop; 28.05.2015
comment
@cHao Нет, это неправда. См. 4.10.2 и 4.5.1 . - person Radiodef; 28.05.2015
comment
@Radiodef: Эй. Итак, подтип означает для Java что-то другое, чем для любого другого языка, который я использую...? Я начинаю вспоминать, почему меня раздражает Java. :П - person cHao; 28.05.2015

Во время выполнения 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 различаются в предыдущих утверждениях

person Silviu Burcea    schedule 28.05.2015
comment
Это никоим образом не решено. Все еще в воздухе. Как пишет Брайан: Это неофициальный набросок предлагаемых улучшений языка Java - person aioobe; 28.05.2015
comment
Если они передумают, я отредактирую/удалю свой пост. Было бы неплохо, если бы у нас была возможность прочитать его мнение по этому вопросу. - person Silviu Burcea; 28.05.2015
comment
Тем не менее, я не понимаю, как ваш ответ относится к вопросу. OP говорит о подтипах (которые статически обрабатываются компилятором), а ваш ответ говорит о времени выполнения. - person aioobe; 28.05.2015
comment
Наследование существует и во время выполнения. Что плохого в другой точке зрения? - person Silviu Burcea; 28.05.2015
comment
ОП спрашивает, верны ли его три пункта, а затем спрашивает Является ли List<Double> подтипом List<? extends Number> и почему? Я не вижу, как вы решаете что-либо из этого в своем ответе. (Дело не в том, чтобы иметь другую точку зрения.) - person aioobe; 28.05.2015
comment
Что бы ни случилось с типами значений, я могу гарантировать вам, что они будут обратно совместимы, и ответ на вопрос ОП будет таким же после того, как они будут введены. - person aioobe; 28.05.2015
comment
На этот раз я не уверен в обратной совместимости... позвольте мне высказать другое мнение по этому поводу, а вы? :) - person Silviu Burcea; 28.05.2015

Это помогло мне увидеть дженерики как ограничения или контракты, а не как типы с подтипами. Таким образом, переменная 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's type is that it is some number type
  ...
  Number ni = list.get(i);
  ...
}

Использование подстановочных знаков ? extends X позволяет смягчить жесткие контракты до более слабых условий.

Используя параметр именованного типа, вы можете установить ограничения на разрешенные типы между несколькими переменными:

// 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);
person Markus    schedule 28.05.2015
comment
Так что не я один так думаю. Думал написать что-то подобное. Спасибо. - person Chop; 28.05.2015