Java Generics "upcast" в непараметризованный тип

Почему я получаю ClassCastException только тогда, когда раскомментируем третье утверждение в Main.main()? И никаких исключений, но хорошо выполненные первое и второе высказывания?

public class Tuple<K, V> {
    public final K first;
    public final V second;

public Tuple(K first, V second) {
    this.first = first;
    this.second = second;
}

@Override public String toString() {
    return "Tuple{" + "first = " + first + ", second = " + second + '}';
    }
}

class Test { static Tuple f(){return new Tuple("test", 8);} }

class Bar {}

class Main{
    public static void main(String[] args) {
        Tuple<String, Bar> t = Test.f();
        System.out.println(t);
      //System.out.println(t.second.getClass().getSimpleName());
    }
}

Заранее спасибо.

2 ответа

Решение

Когда вы пишете цепочку вызовов методов:

System.out.println(t.second.getClass().getSimpleName());

компилятор эффективно расширяет это до:

TypeOfTSecond tmpTSecond = t.second;
Class<?> clazzTmp = tmp.getClass();
String nameTmp = clazzTmp.getSimpleName();
System.out.println(nameTmp);

Теперь, если это произойдет, что t.second является универсальным типом, компилятор вставит приведение к типу t.second будет:

Bar tmpTSecond = (Bar) t.second;

Так что даже если вы никогда не получите доступ к любому Bar-специфичный функционал, вы получите ClassCastException,


Чтобы продемонстрировать это, вот байт-код:

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2        // Method Test.f:()LTuple;
       3: astore_1
       4: getstatic     #3        // Field java/lang/System.out:Ljava/io/PrintStream;
       7: aload_1
       8: getfield      #4        // Field Tuple.second:Ljava/lang/Object;
      11: checkcast     #5        // class Bar
      14: invokevirtual #6        // Method java/lang/Object.getClass:()Ljava/lang/Class;
      17: invokevirtual #7        // Method java/lang/Class.getSimpleName:()Ljava/lang/String;
      20: invokevirtual #8        // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      23: return

Строка 8 - это где t.second помещается в стек; Строка 11, где приведение к Bar происходит.


Это происходит только из-за необработанных типов, используемых при объявлении test.f():

static Tuple f(){return new Tuple("test", 8);}

Если бы это было правильно объявлено как

static Tuple<String, Integer> f(){return new Tuple<>("test", 8);}

тогда эта строка

Tuple<String, Bar> t = Test.f();

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


Основной урок на вынос никогда не использовать сырые типы.

Второй урок - обратите внимание на предупреждения вашего компилятора (или IDE). Компилируя этот код, мне сказали:

Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

и когда я перекомпилировал с этим флагом:

Main.java:19: warning: [unchecked] unchecked call to Tuple(K,V) as a member of the raw type Tuple
      return new Tuple("test", 8);
             ^
  where K,V are type-variables:
    K extends Object declared in class Tuple
    V extends Object declared in class Tuple
Main.java:26: warning: [unchecked] unchecked conversion
    Tuple<String, Bar> t = Test.f();
                                 ^
  required: Tuple<String,Bar>
  found:    Tuple
2 warnings

Согласно Java-документам, для getClass() метод в Object учебный класс

Фактический тип результата Class<? extends |X|> где |X| стирание статического типа выражения, для которого вызывается getClass.

В части объявления тип является Bar

Tuple<String, Bar> t = Test1.f();

Поскольку между родителями и детьми нет Integer а также Bar при попытке привести Integer к Bar вы получаете исключение ClassCastException (т.е. Integer extends Bar не является правильным)

Вот исправление

Tuple<String, Object> t = Test1.f();

Примечание: не рекомендуется использовать необработанные типы. Этот ответ просто объясняет причину неудачи и исправления.

Другие вопросы по тегам