Смущает дженерики Java, требующие приведения

Меня смущает следующий код:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class GenericsTest<T extends List> {

  public void foo() {
    T var = (T) new LinkedList();
  }

  public static void main(String[] args) {
      GenericsTest<ArrayList> gt1 = new GenericsTest<ArrayList>();
      gt1.foo();
      System.out.println("Done");
  }
}

Тип времени выполнения T выглядит java.util.List, независимо от того, какой параметр Type я передал конструктору.

Итак, почему компилятор требует преобразования в T при назначении var? Разве он не должен знать во время компиляции, что LinkedList может быть назначен List?

Я понимаю, что это подделка, и понимаю, почему он работал во время выполнения, хотя, похоже, не должен. Меня сбивает с толку, почему компилятор требует, чтобы я набирал (T) при выполнении задания? Тем не менее, он отлично компилируется без ложного приведения.

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


person Eric Rosenberg    schedule 23.04.2009    source источник


Ответы (4)


В комментарии автор спрашивает:

Однако, по-видимому, компилятор знает это, так зачем ему приводить к (T). Есть ли какой-нибудь способ, при котором актерский состав может когда-нибудь потерпеть неудачу?

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

В этом примере нет причин использовать универсальные шаблоны, поскольку ни один из API не использует переменную типа T. Посмотрите на более реалистичное применение дженериков.

   public class GenericsTest<T extends List> {

 3   public T foo() {
 4     T var = (T) new LinkedList();
 5     return var;
 6   }

 8   public static void main(String... argv) {
 9     GenericsTest<ArrayList> gt1 = new GenericsTest<ArrayList>();
10     gt1.foo();
11     System.out.println("Test one okay");
12     ArrayList<?> list = gt1.foo();
13     System.out.println("Test two okay");
14   }

   }

ClassCastException выдается в строке 12. ClassCastException без приведения? Вызывающий код совершенно правильный. Недопустимое приведение, ошибка, находится в строке 4 вызванного метода. Но исключение возникло в какое-то время и в очень далеком месте.

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

person erickson    schedule 23.04.2009

LinkedList можно назначить List, но нельзя назначить T.

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

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class GenericsTest<T extends List> {

  Class<T> clazz;

  public GenericsTest(Class<T> clazz) {
    this.clazz = clazz;
  }

  public void foo() {
    T var = clazz.cast(new LinkedList());
  }

  public static void main(String[] args) {
    GenericsTest<ArrayList> gt1 = 
        new GenericsTest<ArrayList>(ArrayList.class);
    gt1.foo();
    System.out.println("Done");
  }
}

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

Обратите внимание, что если вы компилируете с подходящими предупреждениями, компилятор сообщит вам, что ваш исходный код на самом деле ничего не проверял.

Дополнительную информацию об этой и многих других темах можно найти в довольно подробном FAQ по Java Generics.

person Jon Skeet    schedule 23.04.2009
comment
Я пытался найти ссылку на FAQ Анжелики Лангер, но у него есть раздражающая тенденция к зависанию IE7 каждый раз, когда я пробую. - person Michael Myers; 24.04.2009
comment
Спасибо за ответ. Я понимаю, что код был поддельным и не должен работать. Я не могу понять, почему компилятор выдает ошибку, если (T) удаляется, даже если, как вы указали, (T) бессмысленно и всегда будет работать. - person Eric Rosenberg; 24.04.2009
comment
Это не бессмысленно - это заставляет разработчика признать, что типы не обязательно подходят. Предупреждение после добавления приведения говорит разработчику, что на самом деле приведение - всего лишь пластырь; все еще есть проблема, которая ждет, чтобы тебя укусить. - person Jon Skeet; 24.04.2009

Это из-за стирания типа; во время компиляции универсальный параметр превращается в любую его нижнюю границу.

Компилятор пытается сказать вам, что что-то не так. Если бы он сделал то, что, по вашему мнению, должен, вы бы получили ClassCastException - вы не можете разыграть LinkedList на ArrayList.

person Michael Myers    schedule 23.04.2009
comment
Спасибо за ссылку. Это объясняет, почему тип среды выполнения - List ... и я не получаю ClassCastException. Однако, по-видимому, компилятор знает это, так зачем ему приводить к (T). Есть ли какой-нибудь способ, при котором актерский состав может когда-нибудь потерпеть неудачу? - person Eric Rosenberg; 24.04.2009

Я предполагал, что вы пришли с C #? В этом случае универсальные шаблоны Java не являются универсальными шаблонами C #.

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

Таким образом, в этом случае используется List, поскольку вы указываете extends List.

person chakrit    schedule 23.04.2009