【问题标题】:overloading method priority in javajava中方法的重载优先级
【发布时间】:2014-03-23 12:29:17
【问题描述】:

我知道这个问题讨论了很多次,但我还是不明白。

研究这段代码:

public class Main {  
    public static void var(Integer x, int y) {  
        System.out.println("Integer int");  
    }  


    public static void var(int... x) {  
        System.out.println("int... x");  
    }  

    public static void var(Integer... x) {  
        System.out.println("Integer...");  
    }  

    public static void main(String... args) {   
        byte i = 0;  
        Integer i2 = 127;  
        var(i, i2);  
    }  
} 

在我的大脑中遵循规则:

  1. 加宽

  2. 拳击

  3. 装箱+可变参数

根据这条规则,我会做出下一步的行动

1.byte 宽为 int

现在我有 int Integer 并且存在方法需要 Integerint

2.制作拳击

因此。 int -> IntegerInteger -> int 参数

我认为这些论点是适用的并有望看到

Integer int

在输出中。

但我明白了

int ...

为什么?

【问题讨论】:

  • 我希望看到这段代码无法编译,看来我的预期是正确的:ideone.com/Garnnp.
  • @OliCharlesworth 可能是 JDK 问题?
  • @OliCharlesworth 我得到了同样的错误。
  • @gstackoverflow.. 你在哪个 java 版本上编译?
  • @Rohit Jain 抱歉。我更新了主题

标签: java overloading


【解决方案1】:

现在很明显,选择了方法var(int...),而不是var(Integer...)

原因是只允许应用某些转化,并且只能是列表中的这些转化之一,而不是一系列转化。 java编译器不允许先进行加宽基元转换,再进行装箱转换。

Java Language Specification in section 5.3中指定

5.3。方法调用转换

方法调用转换适用于每个参数值 方法或构造函数调用(§8.8.7.1、§15.9、§15.12):类型 参数表达式的类型必须转换为 对应的参数。

方法调用上下文允许使用_其中一个_:

  • 身份转换 (§5.1.1)
  • 扩大的基元转换 (§5.1.2)
  • 扩大参考转换 (§5.1.5)
  • 一个装箱转换(第 5.1.7 节)可选地后跟扩大参考 转化
  • 取消装箱转换(第 5.1.8 节)可选地后跟加宽 原始转换。

编译器的唯一选择是:

  1. 在第一个参数上扩大原始转换
  2. 第二个参数的拆箱转换

这会将(byte, Integer) 变成(int, int)

它不能先将第一个参数byte 转换为int,然后对同一参数应用从intInteger 的装箱转换,因为不允许按顺序进行两次转换。

让我们回过头来看看编译器如何选择调用哪个重载方法。这在JLS 15.12.2 中有描述。 (15.12.1描述了如何找到要搜索的类或接口,但我们已经知道要调用类Main中的静态方法)

编译器选择正确的重载方法的前两个阶段不适用于可变参数(“variable arity”)方法,但第三阶段适用:

第三阶段(§15.12.2.4)允许将重载与 可变参数方法、装箱和拆箱。

第 15.12.4 节相当复杂,但此处适用的规则是:

  • 首先应用非可变参数的规则(不适用于您的情况)
  • 调用中的每个变量参数都必须可以通过方法调用转换(我上面复制的那块)转换为变量参数声明的类型

所以..

  1. 您尝试使用(byte, Integer) 调用名为var 的方法
  2. 编译器查看您的方法var(Integer...)
  3. 它问:我能否将第一个参数 byte 转换为 Integer(方法中声明的参数类型)
  4. 它查看 JLS 5.3 中的规则。它只能应用 5 个列表中的一个转换。它们都不能直接将 byte 转换为 Integer - 它不能执行两个步骤。
  5. 所以编译器决定它不能选择var(Integer...)
  6. 然后它会查看您的其他方法,var(int...)
  7. 根据 JLS 5.3,它可以使用 加宽原语转换将您的第一个参数 byte 转换为 int。这是一个复选标记。
  8. 转到第二个参数 Integer,它发现 JLS 5.3 允许编译器使用 拆箱转换将其转换为 int。所以这也是一个复选标记。
  9. 这是最后一个参数,所以 var(int...) 是一个很好的匹配。
  10. 编译器现在继续查看是否有更多方法与您的调用相匹配。如果还有更多操作,则会导致模棱两可的调用错误。
  11. 但是没有更多名为var 的方法,所以var(int...) 是唯一适用的方法。编译器现在将生成代码以进行必要的转换并调用该方法。

【讨论】:

  • 对我来说已经足够复杂的解释了。你能解释一下简单的动作吗?
  • 编译器怎么看
  • 编译器如何解决重载尝试强制转换的问题
  • 好的,我将添加更多关于它如何选择要调用的方法
  • 不幸的是,有时事情并不那么清楚。如果你真的想知道为什么,你必须深入研究 JLS。否则我的建议是:避免这些情况;只需用不同的名称命名您的两种方法。如果你要花很长时间才能理解,那么下一个看代码的人也会花很长时间,所以最好避免这些情况。
【解决方案2】:

Java 只能做“box and wide”而不是“wide and box”。例如,

  • int --> 数字 [OK!]
    • int 装箱为 Integer 扩展为 Number
  • 字节 --> 整数 [不编译]
    • byte 需要先扩展为 int,然后框为 Integer。 Java 没有 允许它。请注意,您不能对 Byte 执行此 -byte 框 扩展为 Integer(Integer 不是 Byte 的超类)。

因此,在您给定的方法中,第一个参数字节已经使两个 Integer 方法失败。所以,只有 int... 是适用的。

我为演示编写了以下类:

public class Overload{

    public static void primitiveWiden(int x){
        System.out.println("int");
    }
    public static void refWiden(Map m){
        System.out.println("Map");
    }
    public static void priWideAndBox(Integer o){//doesn't work
        System.out.println("Object");
    }
    public static void boxAndRefWide(Number n){//it works
        System.out.println("Number");
    }

    public static void main(String[] args){
        byte b =0;
        int i =0;
        HashMap m = new HashMap();

        primitiveWiden(b);
        refWiden(m);
        priWideAndBox(b);//compile error
        boxAndRefWide(i);

    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-27
    • 1970-01-01
    • 1970-01-01
    • 2013-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多