我有这样一门课:
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 (在模块:java.base中)不能强制转换为java.lang.Long (在模块:java.base中)
为什么我会得到第一个输出?不是也有演员阵容吗?为什么我在第二个输出中得到异常?
PS:我使用Java9;我用JShell尝试了一下,在两个输出上都得到了异常。然后我尝试了IntelliJ集成开发环境,得到了第一个输出,但在第二个输出中出现了异常。
发布于 2017-01-06 19:41:30
IntelliJ显示的行为对我来说很清楚:
您在MyClass
中有一个未经检查的强制转换。这意味着当执行下面这行代码时,new Integer(8)
不会立即转换为Long
,而是转换为erasure 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()
将失败并出现异常,因为尝试通过静态类型Long
访问toString()
。因此,将n强制转换为Long
类型是不可能的(Integer
不能强制转换为Long
)。
执行第二条语句时也会发生同样的事情:
System.out.println(new MyClass<Long>().n.getClass());
尝试通过静态类型Long
访问类型为Long
的getClass
方法(在Object
中声明)。因此,发生了n到类型Long
的强制转换,这会产生强制转换异常。
JShell行为:
我尝试在JShell -Java9 early access Build151上重现第一个output语句的异常:
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-06 19:47:58
这是由于Java擦除造成的。
因为Integer
扩展了Number
,所以编译器接受对N
的强制转换。在运行时,由于N
被Number
替换(由于擦除),所以在n
中存储Integer
是没有问题的。
方法System.out.println
的参数是Object
类型的,所以打印n
的值是没有问题的。
但是,在n
上调用方法时,编译器会添加类型检查,以确保调用正确的方法。因此产生了一个ClassCastException
。
发布于 2017-01-07 14:30:07
异常和无异常都是允许的行为。基本上,这归结于编译器如何擦除语句,无论是像这样没有强制转换的东西:
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
,则强制转换或不强制转换在行为上没有区别。在第一个语句中,它被传递到接受Object
的.println()
版本,并且没有更多特定的println
重载接受Long
或Number
或类似的内容,因此无论参数是被视为Long
还是Object
,都将选择相同的重载。对于第二个语句,.getClass()
是由Object
提供的,因此无论左边的内容是Long
还是Object
,它都是可用的。由于擦除的代码在使用和不使用强制转换的情况下都是有效的,并且使用和不使用强制转换的行为是相同的(假设该对象确实是一个Long
),因此编译器可以选择优化强制转换。
编译器甚至可能在一种情况下有类型转换,而在另一种情况下没有,这可能是因为它只在某些简单的情况下优化了类型转换,而在更复杂的情况下不会费心执行分析。我们不需要纠结于为什么特定的编译器决定为特定的语句编译成一种或另一种形式,因为这两种形式都是允许的,您不应该依赖它以某种方式工作。
https://stackoverflow.com/questions/41504343
复制相似问题