【问题标题】:Java raw type value assigned to generic type run time getClss() method error分配给泛型类型运行时 getClass() 方法错误的 Java 原始类型值
【发布时间】:2017-08-18 12:51:18
【问题描述】:
public class Box<T> {
    private T t;
    public Box(T t){
        this.t = t;
    }
    public void add(T t) {
      this.t = t;
    }
    public T get() {
      return t;
    }
    public static void main(String[] args) {
      Box<Integer> b = new Box(new String("may be"));
      System.out.println(b.get()); // successfully print out "may be"
      System.out.println(b.get().getClass()); // error
   }
}

这段代码给出了运行时错误:

exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
  1. 为什么b.get() 没有触发运行时错误?
  2. 为什么只有在我尝试获取类变量的类时才会出现运行时错误?

更准确地说:为什么编译器在 get()(导致异常)checkcast指令插入字节码?

【问题讨论】:

  • 我假设 get 只是为您提供指向 SOME 对象的指针,并且在您的示例中调用 toString 它,但 getClass 实际上检查并解析类型。你期望一个Integer,但对象是String,所以getClass在内部强制转换它并失败。
  • @GhostCat 更清晰!
  • @GhostCat 去吧。我一点也不介意。感谢改进
  • @GhostCat 是的,它将它插入第二个get,因为类型检查来自getClass。第一行不会引发异常,因为 toString 由所有 Object 类型实现。您调用的任何尝试解析类型的方法都会引发异常。

标签: java generics casting runtime


【解决方案1】:

请注意:

  • 在第一种情况下,get() 的结果用于println(Object):换句话说:接收方期望一个对象,并且“条件”将始终为真。
  • 在第二种情况下,将随后对返回的对象进行 方法 调用。现在,如果返回的类型是预期的,它会产生巨大的不同。因此编译器添加了这个检查来保证下面的方法调用是sound

作为背景,可以查看 Java 语言规范,第 5.52 章:

演员表是检查演员表。

这样的转换需要运行时有效性检查。如果运行时的值为 null,则允许强制转换。否则,令 R 为运行时引用值所引用的对象的类,令 T 为强制转换运算符中命名的类型的擦除(第 4.6 节)。强制转换必须在运行时通过 §5.5.3 中的算法检查类 R 与类型 T 的赋值兼容。

分别为第 5.53 章Checked Casts at Run Time

【讨论】:

  • 预期的返回类型是Class&lt;T&gt;,当它被声明为&lt;Integer&gt;时返回Class&lt;String&gt;是没有意义的,因此演员表
【解决方案2】:

这个变量声明不一致。

  Box<Integer> b = new Box(new String("may be")); :

实例化的对象是原始的,因此编译器会发出警告,但允许将原始类型分配给泛型变量:Box&lt;Integer&gt;

b.get() 不会失败,因为您没有将结果分配给变量。
所以编译器不需要将它转换为任何东西。

试试看:

Integer value = b.get();

它会编译得很好,但你会在运行时得到相同的异常 JVM 会尝试将值转换为 Integer:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

当你调用时:

System.out.println(b.get().getClass()); // error

事情已经接近了。

为什么这里需要 Integer 类?

在编译时,b 被声明为Box&lt;Integer&gt; b
所以编译器知道b 是用Integer 类型参数化的。
所以是的,在编译之后,泛型会被删除,但编译器会根据声明的代码添加一些强制转换。

这里就是这种情况。
您在使用Integer 参数化的变量上调用getClass()
但是Class 是一个泛型类:Class&lt;T&gt;
因此编译器添加了对Integer 的强制转换以符合Class&lt;Integer&gt;

当然,通过对变量声明和实例化都使用泛型:

  Box<Integer> b = new Box<>(new String("may be"));

这种不一致是不可能的,因为编译器会立即阻止您。

【讨论】:

  • 为什么 getClass() 与 get() 不同,因为 getClass() 会进行强制转换,而 get() 不会强制转换,而只会返回对象是什么?在这种情况下,即使 get() 旨在根据声明返回一个 Integer,但它实际上返回了一个 String 而不会引起问题,而 getClass() 确实关心它返回什么类型?感谢您的回答,只是需要澄清一下。
【解决方案3】:

除了这里的答案之外,字节指令checkcast 会在不是Object 的具体类型的类型检查中被调用。例如

 public static void main(String[] args) {
          Box<Integer> b = new Box(new String("may be"));
          doStuff(b.get()); // no checkcast needed - works fine
          doIntegerStuff(b.get()); // run-time error with checkcast
          doStringStuff(b.get()); // compile error
       }

 public static void doStuff(Object object){}
 public static void doStringStuff(String integer){}
 public static void doIntegerStuff(Integer integer){}

【讨论】:

    【解决方案4】:

    在代码行Box&lt;Integer&gt; b = new Box(new String("may be")); 在创建新的 Box 对象时缺少关于类型 Info 的信息,并假定默认为 Object 类。

    Box&lt;String&gt; b = new Box&lt;&gt;(new String());这是正确的做法

    -------- 添加 --------

    Object类的方法getClass()返回运行时Class对象。返回时,您的代码Box&lt;Integer&gt; 使getClass() 方法返回Class&lt;Integer&gt; 而不是Class&lt;String&gt;

    您使用 new B(new String()); 提供了原始类型,因此编译器通过了语法,但在调用 getClass() 时,它会将内部对象 String 强制转换为 Integer 并抛出 RuntimeException

    【讨论】:

    • 我不认为这个问题是“我该如何解决这个问题?”而是“为什么原始类型会以这种特殊方式中断?”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-12
    • 2022-12-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多