Почему List <Number>не является подтипом List <Object>? - PullRequest
9 голосов
/ 06 августа 2009
public void wahey(List<Object> list) {}

wahey(new LinkedList<Number>());

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

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

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

List<Double> ld;
wahey(ld);

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

Это отличается от обычного способа работы Java без обобщений. Например:

Object o;
Double d;
String s;

o = s;
d = (Double) o;

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

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

Ответы [ 6 ]

9 голосов
/ 06 августа 2009

То, что вы делаете в примере «без обобщения», - это приведение, которое дает понять, что вы делаете что-то небезопасное. Эквивалент с дженериками будет:

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

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

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

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

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

List<Double> list_d;
String s;

Foo(list_d, s);

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

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

8 голосов
/ 06 августа 2009

Подумайте, было ли это ...

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

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

4 голосов
/ 06 августа 2009

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

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

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

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

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

Я бы порекомендовал вам взять экземпляр «Обобщения и коллекции Java» Мориса Нафталина и Филиппа Вадлера.

3 голосов
/ 06 августа 2009

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

Это сильно отличается от того, что вы сделали в коде примера, не являющемся универсальным, где вы сказали: относитесь к этой строке как к двойному. Вместо этого вы пытаетесь сказать: трактуйте это (список, который содержит только цифры) как (список, который содержит что-нибудь). И это не так, и компилятор может обнаружить это, поэтому он не позволяет вам сойти с рук.

1 голос
/ 06 августа 2009

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

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

С java.sun.com

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

0 голосов
/ 26 июня 2012

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

...