Смущает Java-дженерики, требующие приведения - PullRequest
3 голосов
/ 24 апреля 2009

Я смущен следующим кодом:

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

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

Ответы [ 4 ]

11 голосов
/ 24 апреля 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 * .

7 голосов
/ 24 апреля 2009

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

Однако, предположительно, компилятор знает, что так почему он должен иметь приведение к (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   }

   }

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

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

1 голос
/ 24 апреля 2009

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

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

0 голосов
/ 24 апреля 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...