java - 当没有接受long的方法时,为啥java将long参数提升为float/double?

Posted

技术标签:

【中文标题】java - 当没有接受long的方法时,为啥java将long参数提升为float/double?【英文标题】:Why does java promote long parameter to float/double when there's no method which accepts long?java - 当没有接受long的方法时,为什么java将long参数提升为float/double? 【发布时间】:2013-01-11 01:49:39 【问题描述】:

这是一个SSCCE,它演示了所描述的(恕我直言,奇怪的)行为:

public class Test 

   public static void print(int param) 
       System.out.println("int");
   

   public static void print(float param) 
       System.out.println("float");
   

   public static void print(Long param)  //<--Wrapper type
       System.out.println("Long");
   
   public static void main(String[] args) 
       long param = 100L;
       print(param);  // output == float
   
 

为什么java会这样做?

【问题讨论】:

【参考方案1】:

Java Language Specification 对此非常清楚(强调我的):

15.12.2 编译时步骤 2:确定方法签名

[...]

    第一阶段(§15.12.2.2)在不允许的情况下执行重载解决 装箱或拆箱转换[...]如果在此阶段未找到适用的方法,则继续处理 到第二阶段。 [...]

    第二阶段(第 15.12.2.3 节)执行重载解决方案,同时 允许 装箱和拆箱 [...]

    第三阶段 (§15.12.2.4) 允许将重载与变量相结合 arity 方法、装箱和拆箱。

也就是说,在第一步中只有print(int)print(float) 是合适的。后者匹配,不做进一步调查。


JLS 中也解释了此类规则的原因:

这保证了在 Java SE 5.0 之前在 Java 编程语言中有效的任何调用都不会因为引入可变数量方法、隐式装箱和/或拆箱而被视为模棱两可。

假设您的 Test 类是针对 Java 1.4 编译的(在自动装箱之前)。在那种情况下,很明显:必须选择print(float)(假设我们同意为什么longfloat 被认为是安全的并且可以是隐式的......)因为print(Long)long 参数完全不兼容。

稍后您针对 Java 5+ 编译相同的代码。编译器可以:

在这种情况下选择print(Long) 更“明显”。因此,升级到 Java 5 后,您的程序的行为会有所不同...

产生编译错误,因为调用不明确。因此,以前正确的代码不再在 Java 5 下编译(AFAIR 从未如此)

...或保留旧语义并调用与 Java 1.4 下相同的方法

您现在应该明白为什么使用print(float) - 因为它会在 Java 1.4 下被选择。而且 Java 必须向后兼容。

【讨论】:

我不认为第一个重载比后两个更匹配(除了第一个重载可以通过适当的向下转换执行) 仅供参考如果您从示例中删除float 重载,它会选择Long 而不是int:ideone.com/x6HZ1Q @PaulBellora:感谢这个有趣的观察,它证明了上面的规则是有效的。 print(int) 单独与 long 完全不兼容(尝试删除 print(Long) - 编译错误),因此编译器进入阶段 2。 +1 这似乎表明它确实如此,因为它确实如此。您能否澄清一下您认为为什么会这样做? @PeterLawrey:您正确地指出了向后兼容性,我提供了一些更详尽的解释,谢谢!【参考方案2】:

查看文档

Chapter 5. Conversions and Promotions

5.1.2。扩大基元转换

原始类型的 19 种特定转换称为扩展 原始转换:

byte 到 short、int、long、float 或 double 短于 int、long、float 或 double char 转换为 int、long、float 或 double int 到 long、float 或 double long to float 或 double 浮点数加倍

所以longfloat的转换形式符合规则。

【讨论】:

【参考方案3】:

Tomasz Nurkiewicz 指向规范的相关部分 (15.12.2 in Java SE 7 JLS),但为什么要这样做?为了向后兼容,面向 1.4 和更早版本的源代码应继续调用相同的重载方法。因此,必须忽略 1.5 的功能,并且只有在代码无法编译时才应考虑自动装箱。

至于为什么从longfloat 的转换可能是隐式的——这只是一个有问题的设计选择。

【讨论】:

根本问题的设计选择是决定即使在重载方法或运算符可以采用多种类型的情况下,可用的隐式转换也必须是合法的(没有诊断)。这种设计选择导致 Java 需要对 float f=(float)(1.0/10.0); 进行愚蠢的转换,但对于 double d=1.0f/10.0f;if (f==d)... 则不需要。第一个表达式中的强制转换并没有提高可读性,也没有引起对潜在错误的注意;相比之下,如果我在进行代码审查并且代码应该具有...所隐含的语义 ...后两者,我希望将第二个表达式写成double f=(double)(float)(1.0f/10.0f) and the third as if ((double)f==d), since in the former case the *intended* meaning could likely have been to perform the division as double`(并转换为double,就其本身而言,不会澄清这一点);在后一种情况下,如果没有类型转换,则不清楚比较是否旨在测试 f 是否保留从 d 复制时所保留的值,或者 d 是否保留从 @ 复制时所保留的值987654333@。通过重载限制隐式转换的使用... ...会让编译器在有意义的时候执行隐式转换,并在没有意义的地方大声疾呼。如果可以为全部或部分源文件设置选项,我认为可以在不破坏兼容性的情况下改进行为。【参考方案4】:

它选择 float 而不是 Long 的原因是后来添加了自动装箱,并且出于向后兼容性的原因,它必须进行与以往相同的调用。

【讨论】:

以上是关于java - 当没有接受long的方法时,为啥java将long参数提升为float/double?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 ja.lproj 不显示日语?

为啥要池化无状态会话 bean?

java编译 为啥显示找不到文件

为啥当未接受的套接字发送消息时,套接字不会抛出错误?

java中的main方法为啥接受无效的String args

当 Row 接受可变参数时,为啥 Scala 编译器会失败并显示“此处不允许 ': _*' 注释”?