【问题标题】:Java Generics "upcast" to unparameterized typeJava 泛型“向上转换”为未参数化类型
【发布时间】:2016-08-04 19:02:13
【问题描述】:

为什么只有在 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 中不起作用。您的泛型没有被赋予类型,它们是泛型。如果你有类似public final K&lt;String&gt; first; 的东西,这会起作用,它会返回字符串。
  • Test.f() 返回原始类型。如果你避免使用原始类型,这个错误会更容易被发现。 Test.f() 的返回类型应定义为 Tuple&lt;String, Integer&gt; 以匹配返回的值。这将在Tuple&lt;String, Bar&gt; t = Test.f() 上导致编译错误。类型安全是一项强大的功能。
  • 你应该发布完整的错误:Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to Bar - 现在它变得更加清晰......

标签: java generics compilation jvm rtti


【解决方案1】:

当你编写一个方法调用链时:

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

【讨论】:

  • 请注意,在这种情况下,编译器不需要t.second 的转换插入Bar。这样做是依赖于实现的。
【解决方案2】:

根据Java Docs,对于Object类中的getClass()方法

实际结果类型是 Class&lt;? extends |X|&gt;,其中 |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。这个答案只是解释了失败的原因和解决方法。

【讨论】:

  • 投反对票是没有用的,除非投反对票的人澄清了问题所在
  • 我没有投反对票,但我猜这是因为您的修复是基于原始类型的使用。如果 Test.f() 声明的类型参数与返回的值 (&lt;String, Integer&gt;) 匹配,则使用 &lt;String, Object&gt; 时会出现编译错误。虽然它有效,但它鼓励了糟糕的设计,因为它避免了实际问题(缺乏类型安全性),这会通过你得到的警告消息显示出来。我个人不会因此而投反对票(它仍然有效),但有些人比其他人更严格。
  • 我明白了!因此,在 SO,我们不仅要提供修复并解释修复背后的原因,还必须提出最佳实践建议。就像你说的,我没说不鼓励使用原始类型。谢谢!
  • 没错。我个人认为它不应该被否决,因为它仍然有效,但如果答案鼓励更好的做法,我可能会投赞成票。
  • @VinceEmigh,我用附加说明更新了我的答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-29
  • 2021-12-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多