首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Java泛型:为什么这样的输出是可能的?

Java泛型:为什么这样的输出是可能的?
EN

Stack Overflow用户
提问于 2017-01-06 19:06:29
回答 4查看 2.8K关注 0票数 51

我有这样一门课:

代码语言:javascript
复制
class MyClass<N extends Number> {
    N n = (N) (new Integer(8));
}

我想要得到这些输出:

代码语言:javascript
复制
System.out.println(new MyClass<Long>().n);
System.out.println(new MyClass<Long>().n.getClass());

第一条System.out.println()语句的

  1. 输出:

8第二条System.out.println()语句的

  • 输出:

java.lang.ClassCastException: java.lang.Integer (在模块:java.base中)不能强制转换为java.lang.Long (在模块:java.base中)

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

PS:我使用Java9;我用JShell尝试了一下,在两个输出上都得到了异常。然后我尝试了IntelliJ集成开发环境,得到了第一个输出,但在第二个输出中出现了异常。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2017-01-06 19:41:30

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

您在MyClass中有一个未经检查的强制转换。这意味着当执行下面这行代码时,new Integer(8)不会立即转换为Long,而是转换为erasure Number (这是有效的):N n =(N)(new Integer(8));

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

代码语言:javascript
复制
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)。

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

代码语言:javascript
复制
System.out.println(new MyClass<Long>().n.getClass()); 

尝试通过静态类型Long访问类型为LonggetClass方法(在Object中声明)。因此,发生了n到类型Long的强制转换,这会产生强制转换异常。

JShell行为:

我尝试在JShell -Java9 early access Build151上重现第一个output语句的异常:

代码语言:javascript
复制
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-无异常。

票数 39
EN

Stack Overflow用户

发布于 2017-01-06 19:47:58

这是由于Java擦除造成的。

因为Integer扩展了Number,所以编译器接受对N的强制转换。在运行时,由于NNumber替换(由于擦除),所以在n中存储Integer是没有问题的。

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

但是,在n上调用方法时,编译器会添加类型检查,以确保调用正确的方法。因此产生了一个ClassCastException

票数 25
EN

Stack Overflow用户

发布于 2017-01-07 14:30:07

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

代码语言:javascript
复制
System.out.println(new MyClass().n);
System.out.println(new MyClass().n.getClass());

或者像下面这样的强制转换:

代码语言:javascript
复制
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重载接受LongNumber或类似的内容,因此无论参数是被视为Long还是Object,都将选择相同的重载。对于第二个语句,.getClass()是由Object提供的,因此无论左边的内容是Long还是Object,它都是可用的。由于擦除的代码在使用和不使用强制转换的情况下都是有效的,并且使用和不使用强制转换的行为是相同的(假设该对象确实是一个Long),因此编译器可以选择优化强制转换。

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41504343

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档