51

我有这堂课:

class MyClass<N extends Number> {
    N n = (N) (new Integer(8));
}

我想得到这些输出:

System.out.println(new MyClass<Long>().n);
System.out.println(new MyClass<Long>().n.getClass());
  1. 第一条System.out.println()语句的输出:

    8
    
  2. 第二条System.out.println()语句的输出:

    java.lang.ClassCastException: java.lang.Integer (in module: java.base)
        cannot be cast to java.lang.Long (in module: java.base)
    

为什么我得到第一个输出?不是也有演员表吗?为什么我在第二个输出中得到异常?

PS:我使用Java 9;我用 JShell 试了一下,两个输出都出现异常。然后我用 IntelliJ IDE 进行了尝试,得到了第一个输出,但第二个输出异常。

4

4 回答 4

39

IntelliJ 显示的行为对我来说很清楚:

你有一个未经检查的演员表MyClass。当执行此行时,这意味着new Integer(8)不会立即Long转换为擦除Number(有效):N n =(N)(new Integer(8));

现在让我们看看输出语句:

System.out.println(new MyClass<Long>().n);

归结为String.valueOf(new MyClass<Long>().n)->((Object)new MyClass<Long>().n).toString()工作正常,因为 n 是通过访问的,Object并且该toString()方法是通过静态类型访问的Object-> 不会Long发生强制转换。new MyClass<Long>().n.toString()将失败并出现异常,因为toString()试图通过静态类型访问Long。因此,发生 n 到 type 的强制转换Long是不可能的(Integer不能强制转换为Long)。

执行第二条语句时也会发生同样的事情:

System.out.println(new MyClass<Long>().n.getClass()); 

尝试通过静态类型访问类型的getClass方法(在 中声明Object)。因此,发生类型转换的 n 会产生转换异常。LongLongLong

JShell 行为:

我试图重现 JShell - Java 9 early access Build 151 上第一个输出语句的异常:

jshell> class MyClass<N extends Number> {
   ...>     N n = (N) (new Integer(8));
   ...> }
|  Warning:
|  unchecked cast
|    required: N
|    found:    java.lang.Integer
|      N n = (N) (new Integer(8));
|                ^--------------^
|  created class MyClass

jshell> System.out.println(new MyClass<Long>().n);
8

jshell> System.out.println(new MyClass<Long>().n.getClass());
|  java.lang.ClassCastException thrown: java.base/java.lang.Integer cannot be cast to java.base/java.lang.Long
|        at (#4:1)

但似乎 JShell 给出的结果与 IntelliJ 完全相同。System.out.println(new MyClass<Long>().n);输出 8 - 也不例外。

于 2017-01-06T11:41:29.907 回答
25

发生这种情况是因为 Java 擦除。

由于Integerextends Number,编译器接受强制转换为N. 在运行时,由于N被替换为(由于擦除),存储一个insideNumber没有问题。Integern

方法的参数System.out.println是类型的Object,所以打印 的值没有问题n

但是,在调用方法时n,编译器会添加类型检查以确保调用正确的方法。因此导致ClassCastException.

于 2017-01-06T11:47:58.240 回答
1

异常和无异常都是允许的行为。基本上,这取决于编译器如何擦除语句,无论是像这样没有强制转换的东西:

System.out.println(new MyClass().n);
System.out.println(new MyClass().n.getClass());

或者像这样的演员:

System.out.println((Long)new MyClass().n);
System.out.println(((Long)new MyClass().n).getClass());

或者一个代表一个陈述,一个代表另一个。这两个版本都是可以编译的有效 Java 代码。问题是编译器是否允许编译为一个版本,或另一个版本,或两者兼而有之。

允许在此处插入强制转换,因为这通常是当您从类型为类型变量的通用上下文中获取某些内容并将其返回到类型变量采用特定类型的上下文中时发生的情况。例如,您可以分配给new MyClass<Long>().n一个没有任何强制转换的类型变量Long,或者传递new MyClass<Long>().n到一个没有任何强制转换的预期位置Long,这两种情况显然都需要编译器插入一个强制转换。当你有 时,编译器可以决定总是插入一个强制new MyClass<Long>().n转换,这样做并没有错,因为表达式应该有 type Long

另一方面,在这两个语句中也可以不进行强制转换,因为在这两种情况下,表达式都在Object可以使用 any 的上下文中使用,因此不需要强制转换来使其编译和类型安全。此外,在这两个语句中,如果值确实是 a ,则强制转换或不强制转换不会对行为产生影响Long。在第一个语句中,它被传递给 that take 的版本.println()Object并且没有更具体的printlnthat take LongorNumber或类似的重载,因此无论参数是否被视为Longor ,都将选择相同的重载Object。对于第二个语句,.getClass()是由 提供的Object,所以不管左边的东西是不是LongObject。由于擦除的代码在有和没有强制转换的情况下都是有效的,并且有和没有强制转换的行为都是相同的(假设事情确实是 a Long),编译器可以选择优化强制转换。

编译器甚至可以在一种情况下进行强制转换,而在另一种情况下则没有,这可能是因为它只在某些简单情况下优化了强制转换,而在更复杂的情况下不费心执行分析。我们不需要详述为什么特定编译器决定为特定语句编译成一种或另一种形式,因为两者都是允许的,您不应该依赖它以一种或另一种方式工作。

于 2017-01-07T06:30:07.410 回答
0

发生这种情况是因为您已经将 n 定义为整数对象,因此它不会将其转换为 long

MyClass要么在 sysout中使用 Integer,如

System.out.println(new MyClass<Integer>().n);

或定义n为:N n =(N)(new Long(8));.

于 2017-01-06T11:39:31.697 回答