Вопрос

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;

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

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

Это было полезно?

Решение

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

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 собирается делать со своими параметрами.

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

Другие советы

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

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) {}

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

Я бы порекомендовал вам приобрести книгу «Java Generics and Collections» Мориса Нафталина и Филипа Уодлера.

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

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

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

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

От java.sun.com

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top