Java 泛型 "upcast" 到未参数化类型

Java Generics "upcast" to unparameterized type

为什么我只在取消注释 Main.main() 中的第三条语句时得到 ClassCastException?没有例外,但是执行得很好的第一条和第二条语句?

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());
    }
}

提前致谢。

根据 Java Docs,对于 Object class

中的 getClass() 方法

实际结果类型为Class<? extends |X|> 其中|X|是对表达式静态类型的擦除调用了哪个 getClass。

声明部分,类型为Bar

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

由于 IntegerBar 之间没有父子关系,在尝试将 Integer 转换为 Bar 时,您会得到 ClassCastException(即 Integer extends Bar 不正确)

这是解决方法

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

注:It is not encouraged to use raw types。这个答案只是解释了失败背后的原因和解决方法。

当您编写一系列方法调用时:

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();

无法编译。但是原始类型的使用禁用了编译器的类型检查,所以不能保证避免这样的运行时错误。


主要外卖课程是never use raw types

第二个教训是注意编译器(或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