Почему List не является подтипом List?

public void wahey(List<Object> list) {}

wahey(new LinkedList<Number>());

вызов метода не будет вводить-check. Я даже не могу привести параметр следующим образом:

wahey((List<Object>) new LinkedList<Number>());

из моих исследований я понял, что причина, по которой это не разрешено,-безопасность типа. Если бы нам было позволено сделать вышеизложенное, то мы могли бы иметь следующее:

List<Double> ld;
wahey(ld);

внутри метода wahey мы могли бы добавить некоторые строки во входной список (поскольку параметр поддерживает List<Object> ссылка). Теперь, после вызова метода, ld ссылается на список с тип List<Double>, но фактический список содержит некоторые строковые объекты!

это похоже на обычный способ работы Java без дженериков. Например:

Object o;
Double d;
String s;

o = s;
d = (Double) o;

то, что мы делаем здесь, по сути, то же самое, за исключением того, что это пройдет проверку времени компиляции и потерпит неудачу только во время выполнения. Версия со списками не будет компилироваться.

это заставляет меня полагать, что это чисто дизайнерское решение в отношении ограничений типа на дженерики. Я надеялся получить какие-то комментарии по этому решению?

6 ответов


то, что вы делаете в Примере "без дженериков",-это бросок, который дает понять, что вы делаете что-то небезопасное для типа. Эквивалентом дженериков было бы:

Object o;
List<Double> d;
String s;

o = s;
d.add((Double) o);

который ведет себя одинаково (компилируется, но не выполняется во время выполнения). Причина, по которой вы не позволяете поведение, о котором вы спрашиваете, заключается в том, что это позволит подразумевается type-небезопасные действия, которые намного сложнее заметить в коде. Например:

public void Foo(List<Object> list, Object obj) {
  list.add(obj);
}

Это прекрасно выглядит отлично и безопасно, пока вы не назовете это так:

List<Double> list_d;
String s;

Foo(list_d, s);

который также выглядит типобезопасным, потому что вы, как вызывающий абонент, не обязательно знаете, что Foo собирается делать с его параметрами.

Итак, в этом случае у вас есть два, казалось бы, безопасных типа бита кода, которые вместе в конечном итоге небезопасны для типа. Это плохо, потому что это скрыто и поэтому трудно избежать и сложнее отладить.


рассмотрим, если это было...

List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException

вы сможете добавить в список что-либо из родительского типа, которое может быть не тем, что было ранее объявлено, что, как показано в приведенном выше примере, вызывает всевозможные проблемы. Таким образом, это не допускается.


Они не являются подтипами друг друга из-за того, как дженерики работы. Вы хотите объявить свою функцию следующим образом:

public void wahey(List<?> list) {}

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

public void wahey(List<? extends Number> list) {}

Это позволит вам взять в списки что-то, что является подклассом числа.

Я бы рекомендовал вам забрать копию "Java Generics and Collections" Мориса нафталина и Филиппа Уодлера.


здесь по существу два измерения абстракции: абстракция списка и абстракция его содержания. Совершенно нормально варьировать абстракцию списка - скажем, например, что это LinkedList или ArrayList - но не нормально дополнительно ограничивать содержимое, чтобы сказать: это (список, который содержит объекты) является (связанный список, который содержит только числа). Потому что любая ссылка, которая знает ее как (список, который содержит объекты), понимает по контракту ее типа, что он может держать любой


" то, что мы здесь делаем, по существу то же самое, только это пройдет. проверка времени компиляции и только сбой при время выполнения. Версия со списками не будет компилировать."

то, что вы наблюдаете, имеет смысл, если вы считаете, что основная цель Java generics-получить несовместимость типов для сбоя во время компиляции вместо времени выполнения.

от java.sun.com

Generics обеспечивает путь для вас сообщить тип коллекции компилятору, так что это может быть проверен. Как только компилятор узнает тип элемента коллекции, компилятор может проверить, что вы воспользовались собрание последовательно и может вставить правильные слепки значений вынимается из коллекции.


В Java, List<S> не подтипом List<T>, когда S является подтипом T. Это правило обеспечивает безопасность типов.

допустим, мы допустим List<String> подтипом List<Object>. Рассмотрим следующий пример:

public void foo(List<Object> objects) {
    objects.add(new Integer(42));
}

List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);

таким образом, форсирование этого обеспечивает безопасность типа, но также имеет недостаток, он менее гибкий. Рассмотрим следующий пример:

class MyCollection<T> {
    public void addAll(Collection<T> otherCollection) {
        ...
    }
}

здесь у вас есть коллекция T's, вы хотите добавить все предметы из другой коллекции. Вы не можете вызвать этот метод с Collection<S> на S подтип T. В идеале это нормально, потому что вы только добавляете элементы в свою коллекцию, вы не изменяете коллекцию параметров.

чтобы исправить это, Java предоставляет то, что они называют "подстановочными знаками". Подстановочные знаки-это способ обеспечения ковариации / контравариации. Теперь рассмотрим следующие подстановочные знаки:

class MyCollection<T> {
     // Now we allow all types S that are a subtype of T
     public void addAll(Collection<? extends T> otherCollection) {
         ...

         otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
     }
} 

теперь с помощью подстановочных знаков мы разрешаем ковариацию в введите T, и мы заблокируем операции, которые не являются безопасными для типа (например, добавление элемента в коллекцию). Таким образом мы получаем гибкость и безопасность типов.