Javagenerics:为什么这个输出成为可能?
我有这个class级:
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)
为什么我得到第一个输出? 还有没有演员? 为什么我会在第二个输出中得到exception?
PS:我使用Java 9; 我尝试了与JShell,我得到了两个输出exception。 然后我用IntelliJ IDE试了一下,得到了第一个输出,但第二个是exception。
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()
方法是通过静态typesObject
– >不能转换为Long
来访问的。 new MyClass<Long>().n.toString()
将会失败并产生exception,因为toString()
试图通过静态typesLong
来访问。 因此一个ntypes的Long
types转换是不可能的( Integer
不能被强制转换为Long
)。
执行第二条语句时会发生同样的情况:
System.out.println(new MyClass<Long>().n.getClass());
Long
types的getClass
方法(在Object
声明)试图通过静态typesLong
来访问。 因此,发生ntypes为Long
types转换会产生抛出exception。
JShell的行为:
我试图重现JShell的第一个输出语句产生的exception – Java 9早期访问构build151:
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 – 没有例外。
这是由于Java擦除而发生的。
由于Integer
扩展了Number
,所以编译器将转换接受为N
在运行时,由于N
被Number
replace(由于擦除),因此在n
内部存储Integer
是没有问题的。
方法System.out.println
的参数是Object
types的,所以打印n
的值没有问题。
但是,在n
上调用方法时,编译器会添加一个types检查,以确保调用正确的方法。 因此导致ClassCastException
。
例外和例外都是允许的行为。 基本上,这归结为编译器如何擦除语句,无论是否是这样的没有强制转换:
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代码。 问题是,编译器是可以编译为一个版本,还是两者兼而有之。
在这里插入一个转换是允许的,因为当你从types是typesvariables的通用上下文中取出某些东西时,通常会发生什么情况,并将其返回到typesvariables采用特定types的上下文中。 例如,可以将new MyClass<Long>().n
分配给new MyClass<Long>().n
types的variables,而不进行任何强制转换,或将new MyClass<Long>().n
转换为期望Long
的地方,而不需要任何强制转换显然会要求编译器插入一个强制转换。 当你有new MyClass<Long>().n
,编译器可以决定总是插入一个转换,而这样做并不是错的,因为expression式应该是Long
types的。
另一方面,也可以在这两个语句中不进行强制转换,因为在这两种情况下,expression式都可以在任何Object
可以使用的上下文中使用,所以不需要强制转换就可以编译并且是types安全的。 而且,在这两个陈述中,如果价值确实是Long
,那么投行或非投行都不会造成行为上的差异。 在第一个语句中,它被传递给接收Object
的.println()
的版本,并且没有更多的具有Long
或Number
或者类似的东西的println
重载,所以同样的重载将被select,无论参数被认为是Long
或Object
。 对于第二个语句, .getClass()
是由Object
提供的,所以它是可用的,不pipe左边的东西是Long
还是Object
。 由于被擦除的代码在有和没有转换的情况下都是有效的,并且在没有转换的情况下(假设该东西确实是一个Long
),行为将是相同的,编译器可以select优化转换。
编译器甚至可以在一种情况下进行强制转换,而不是在另一种情况下进行强制转换,也许是因为在某些简单的情况下,编译器只会对演员进行优化,而不会在更复杂的情况下执行分析。 我们不需要仔细研究为什么一个特定的编译器为了某个特定的语句而决定编译成某种forms,因为两者都是可以允许的,你不应该依靠它来以某种方式工作。
这是因为你已经定义了n作为整数的对象,所以它不会把它转换为long
或者在sysout中的MyClass
中使用Integer
System.out.println(new MyClass<Integer>().n);
或将n
定义为: N n =(N)(new Long(8));
。