我有这堂课:
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());
输出第一个System.out.println()
声明:
8
输出第二个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尝试了它并得到了第一个输出,但第二个是异常。
答案 0 :(得分: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)
- &gt; ((Object)new MyClass<Long>().n).toString()
工作正常,因为n是通过Object
访问的,toString()
方法是通过静态类型Object
访问的 - &gt;没有强制转换为Long
。 new MyClass<Long>().n.toString()
会因异常而失败,因为尝试通过静态类型toString()
访问Long
。因此,会发生n到Long
类型的转换,这是不可能的(Integer
无法转换为Long
)。
执行第二个语句时会发生同样的事情:
System.out.println(new MyClass<Long>().n.getClass());
尝试通过静态类型getClass
访问类型为Object
的{{1}}方法(在Long
中声明)。因此,会发生类型为Long
的n的转换,这会产生强制转换异常。
JShell行为:
我试图在JShell上重现第一个输出语句的结果异常 - Java 9早期访问Build 151:
Long
但似乎JShell提供了与IntelliJ完全相同的结果。 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)
输出8 - 没有例外。
答案 1 :(得分:25)
这是因为Java擦除而发生的。
由于Integer
扩展Number
,编译器接受转换为N
。在运行时,由于N
替换为Number
(由于删除),因此在Integer
内存储n
没有问题。
方法System.out.println
的参数类型为Object
,因此打印n
的值没有问题。
但是,在n
上调用方法时,编译器会添加类型检查以确保调用正确的方法。因此导致ClassCastException
。
答案 2 :(得分: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
时,编译器可以决定总是插入一个演员表,并且这样做没有错,因为表达式应该有Long
类型。
另一方面,也允许在这两个语句中没有强制转换,因为在这两种情况下,表达式都在可以使用任何Object
的上下文中使用,因此不需要强制转换使它编译并保持类型安全。此外,在两个语句中,如果值确实是Long
,则强制转换或不投射会在行为上产生差异。在第一个语句中,它被传递到.println()
的{{1}}版本,而Object
或println
的特定重载不再存在Long
或者类似的东西,所以无论参数是被视为Number
还是Long
,都会选择相同的重载。对于第二个语句,Object
由.getClass()
提供,因此无论左侧的内容是Object
还是Long
,它都可用。由于删除的代码在有和没有强制转换的情况下都是有效的,并且在有和没有强制转换的情况下行为都是相同的(假设事物确实是Object
),编译器可以选择优化强制转换。
编译器甚至可以在一个案例中进行演员而不在另一个案例中,可能是因为它只是在某些简单的情况下优化了演员表,但在更复杂的情况下并不打算进行分析。我们不需要详述为什么特定的编译器决定为某个特定语句编译成一种形式或另一种形式,因为两者都是允许的,你不应该依赖它以某种方式工作。
答案 3 :(得分:0)
这种情况正在发生,因为您已经将n定义为整数对象,因此不会将其强制转换为long
在sysout中的MyClass
中使用Integer,如
System.out.println(new MyClass<Integer>().n);
或将n
定义为:N n =(N)(new Long(8));
。