31

这是一个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会这样做?

4

4 回答 4

31

Java语言规范对此非常清楚(强调我的):

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

[...]

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

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

  3. 第三阶段(第 15.12.2.4 节)允许将重载与可变参数方法、装箱和拆箱相结合。

也就是说,只在第一步print(int)print(float)就可以适当了。后者匹配,不做进一步调查。


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

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

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

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

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

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

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

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

于 2013-01-27T17:20:10.773 回答
12

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

于 2013-01-27T17:51:45.250 回答
7

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

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

于 2013-01-27T17:35:17.100 回答
5

查看文档

第 5 章转换和促销

5.1.2. 扩大基元转换

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

  • 字节到 short、int、long、float 或 double
  • 短到 int、long、float 或 double
  • char 到 int、long、float 或 double
  • int 到 long、float 或 double
  • 长时间浮动或加倍
  • 浮动加倍

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

于 2013-01-27T17:22:17.643 回答