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();
Примечание: не рекомендуется использовать необработанные типы. Этот ответ просто объясняет причину неудачи и исправления.