【问题标题】:Java overloading - long and floatJava 重载 - long 和 float
【发布时间】:2017-05-05 04:52:39
【问题描述】:

我试图了解 Java 重载规则。除了跟随,一切似乎都很好,

public static void main(String[] args) {
    long aLong = 123L;        
    foo(aLong);
}

private static void foo(double aDouble) {
    System.out.println("Foo aDouble");
}

private static void foo(Long aWrapperLong) {
    System.out.println("Foo Wrapper Long");
}

private static void foo(int anInt) {
    System.out.println("Foo Int");
}

private static void foo(float aFloat) {
    System.out.println("Foo Float");
}

为什么调用解析为foo(float aFloat)。我从 JLS 了解以下内容,

此步骤使用方法的名称和参数的类型 用于定位既可访问又适用的方法的表达式 可能有不止一种这样的方法,在这种情况下,最 选择特定的。

我在这里有意使用 Wrapper Long 而不是原始的 long。大小为 64 位的原始 long 不会以 foo(double aDouble) 结尾,而是以 32 位浮点数结尾 foo(float aFloat)

Why does Java implicitly (without cast) convert a `long` to a `float`?这个问题进一步阐明了这个问题的答案。

【问题讨论】:

  • 非常好的问题,这一点尤其重要,因为并非所有高于 16777215 的值都不能在没有精度损失的情况下以浮点数表示。例如 16777217,当转换为浮点数时将四舍五入为 1.6777216E7。
  • 关于编辑:64 位长与 64 位双精度无关。请参阅about mantissa 了解差异。

标签: java overloading


【解决方案1】:

这是因为JLS #15 中的“最具体”规则,它又指JLS #4.10,而JLS #4.10 又指#4.10.1,其中规定:

以下规则定义了原始类型之间的直接超类型关系:

  • 双>1浮动

  • float >1 long

  • long >1 int

  • int >1 字符

  • int >1

  • 短>1字节

其中“S >1 T”表示“T 是 S 的直接子类型”,根据本节上方的 JLS #4.10。

因此,在这种情况下,在 long 没有直接匹配的情况下,在查看自动装箱之前,编译器会根据上述规则选择最近的可用超类型 float

【讨论】:

  • 如果是这样的话,它不应该选择int方法吗?
  • @KlausGroenbaek 不,因为原始参数是long,这是事情的开始。调用 int 重载需要强制转换。
  • @EJP where "S >1 T" means "S is a direct subtype of T",不是反过来吗?从链接,T is a direct subtype of S, written T <1 S, if S >1 T.
  • “编译器选择最近的可用子类型”你的意思是最近的可用超类型吗?
  • @Michael 不,我没有。例如,如果实际参数为long,则不会选择int
【解决方案2】:

引用JLS

确定适用性的过程始于确定 可能适用的方法(§15.12.2.1)。

流程的其余部分分为三个阶段,以确保 与之前的 Java 编程语言版本的兼容性 Java SE 5.0。阶段是:

  1. 第一阶段 (§15.12.2.2) 执行过载解决方案 允许装箱或拆箱转换,或使用可变参数 方法调用。如果在此阶段没有找到适用的方法 然后处理继续到第二阶段。

这保证了在 Java 编程中有效的任何调用 Java SE 5.0 之前的语言不被认为是模棱两可的结果 引入可变数量方法、隐式装箱和/或 拆箱。但是,变量arity 方法的声明(§8.4.1) 可以更改为给定方法方法调用选择的方法 表达式,因为可变的arity方法被视为固定的 第一阶段的arity方法。例如,声明 m(Object...) 在已经声明 m(Object) 的类中导致 m(Object) 没有 不再为某些调用表达式(例如 m(null))选择,如 m(Object[]) 更具体。

  1. 第二阶段 (§15.12.2.3) 执行过载解决方案,同时 允许装箱和拆箱,但仍然排除使用变量 arity 方法调用。如果在此期间没有找到适用的方法 阶段然后处理继续到第三阶段。 ...

编辑:关于choosing float 而不是 double:

如果多个成员方法既可访问又适用于 方法调用,需要选择一个来提供 运行时方法分派的描述符。 Java 编程 语言使用选择最具体方法的规则。

非正式的直觉是一种方法比 如果可以传递第一个方法处理的任何调用,则另一个 在没有编译时错误的情况下转到另一个。

重载解析的第一阶段会选择这四个方法中的两个

private static void foo(double aDouble) 
private static void foo(float aFloat) 

因为第一阶段不允许装箱/拆箱 (Long),并且您不能在没有显式转换的情况下将 long 传递给带有 int 参数的方法。将选择最具体的方法。在这种情况下,float 方法将被解释为比 double 更具体。

【讨论】:

  • 但是为什么选择float 而不是double
  • 添加到答案中
  • JLS 5.1.2long to float 或 double 所以看起来规则说 float 比 double 更适合转换。编辑:该规则的来源是JLS 4.10.1
  • 如果您展示了在给定情况下如何应用语言规则,这将是一个更好的答案,而不是仅仅将 JLS 的部分内容转储给我们而没有任何解释。语言标准通常以使它们在数学上精确的方式编写,而不是以非编译器编写者容易理解的方式编写。此外,即使我是一个习惯于语言标准的前编译器维护者,我也看不出你引用的部分如何显示为什么选择float。我怀疑您没有引用 JLS 的其他重要部分。
【解决方案3】:

转换有优先规则。 它将选择 widening 而不是 boxing

因此,在这种情况下,编译器会搜索可以接受比 long 原始数据类型更大(最接近可能更大)的参数作为参数的方法,即浮点数。

如果您从发布的示例中删除方法 foo(double aDouble)foo(float aFloat),那么编译器将执行 装箱 和选择 foo(Long aWrapperLong)

【讨论】:

    【解决方案4】:

    加宽优先于重载自动装箱。 Java 从一开始就没有引入用于基元(Integer、Long 等)的包装类,并且由于 Java 支持向后兼容性,它应该以相同的方式在新版本中执行在一个 Java 版本中编写的代码。

    【讨论】:

      猜你喜欢
      • 2013-12-23
      • 1970-01-01
      • 1970-01-01
      • 2019-05-23
      • 1970-01-01
      • 2018-11-30
      • 1970-01-01
      • 2015-02-23
      • 2011-12-22
      相关资源
      最近更新 更多