Java Generics" upcast"到非参数化类型

时间:2016-08-04 19:02:13

标签: java generics compilation jvm rtti

为什么只有在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());
    }
}

提前致谢。

2 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:0)

根据Java DocsgetClass()

中的Object方法

实际结果类型为 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。这个答案只是解释了失败背后的原因和解决方法。