📜  Java Varargs 中的方法重载和歧义

📅  最后修改于: 2022-05-13 01:54:40.494000             🧑  作者: Mango

Java Varargs 中的方法重载和歧义

先决条件——可变参数,方法重载

Varargs 中的方法重载

重载允许不同的方法具有相同的名称,但不同的签名,其中签名可能因输入参数的数量或输入参数的类型或两者而异。我们可以通过以下方式重载一个接受可变长度参数的方法:

  • 案例 1 – 只有可变参数的方法:在这种情况下, Java使用类型差异来确定调用哪个重载方法。如果一个方法签名严格来说比另一个更具体,那么Java会选择它而不会出错。
    //Java program to illustrate 
    //method overloading in varargs
    public class varargsDemo
    {
        public static void main(String[] args)
        {
            fun();
        }
      
        //varargs method with float datatype
        static void fun(float... x)
        {
            System.out.println("float varargs");
        }
          
        //varargs method with int datatype
        static void fun(int... x)
        {
            System.out.println("int varargs");
        }
          
        //varargs method with double datatype
        static void fun(double... x)
        {
            System.out.println("double varargs");
        }
    }
    

    输出:

    int varargs
    

    此输出是由于 int 比 double 更具体。如 JLS 第 15.12.2.5 节所述,如果多个成员方法既可访问又适用于方法调用,则有必要选择一个为运行时方法分派提供描述符。 Java编程语言使用根据类型提升选择最具体的方法的规则。在这种情况下,以下规则定义了原始类型之间的直接超类型关系:

    • 双>浮动
    • 浮动>长
    • 长 > 整数
    • 整数 > 字符
    • 整数 > 短
    • 短>字节
  • 案例 2 – 带有可变参数和其他参数的方法在这种情况下, Java使用参数的数量和参数的类型来确定调用哪个方法。

    下面是三次重载 fun() 方法的Java程序:

    // Java program to demonstrate Varargs 
    // and overloading.
    class Test 
    {
        // A method that takes varargs(here integers).
        static void fun(int ... a) 
        {
            System.out.print("fun(int ...): " +
                    "Number of args: " + a.length +
                    " Contents: ");
              
            // using for each loop to display contents of a
            for(int x : a)
                System.out.print(x + " ");
              
            System.out.println();
        }
          
        // A method that takes varargs(here booleans).
        static void fun(boolean ... a)
        {
            System.out.print("fun(boolean ...) " +
                    "Number of args: " + a.length +
                    " Contents: ");
              
            // using for each loop to display contents of a
            for(boolean x : a)
                System.out.print(x + " ");
              
            System.out.println();
        }
          
        // A method takes string as a argument followed by varargs(here integers).
        static void fun(String msg, int ... a) 
        {
            System.out.print("fun(String, int ...): " +
                    msg + a.length +
                    " Contents: ");
              
            // using for each loop to display contents of a
            for(int x : a)
                System.out.print(x + " ");
              
            System.out.println();
        }
          
        public static void main(String args[])
        {
            // Calling overloaded fun() with different  parameter
            fun(1, 2, 3);
            fun("Testing: ", 10, 20);
            fun(true, false, false);
        }
    }
    

    输出:

    fun(int ...): Number of args: 3 Contents: 1 2 3 
    fun(String, int ...): Testing: 2 Contents: 10 20 
    fun(boolean ...) Number of args: 3 Contents: true false false 
    

可变参数和歧义

重载采用可变长度参数的方法时,有时会导致意外错误。这些错误包含歧义,因为这两种方法都是有效的调用候选者。编译器无法决定将方法调用绑定到哪个方法上。

// Java program to illustrate Varargs and ambiguity
class Test 
{
    // A method that takes varargs(here integers).
    static void fun(int ... a) 
    {
        System.out.print("fun(int ...): " +
                "Number of args: " + a.length +
                " Contents: ");
          
        // using for each loop to display contents of a
        for(int x : a)
            System.out.print(x + " ");
          
        System.out.println();
    }
      
    // A method that takes varargs(here booleans).
    static void fun(boolean ... a)
    {
        System.out.print("fun(boolean ...) " +
                "Number of args: " + a.length +
                " Contents: ");
          
        // using for each loop to display contents of a
        for(boolean x : a)
            System.out.print(x + " ");
          
        System.out.println();
    }
      
    public static void main(String args[])
    {
        // Calling overloaded fun() with different  parameter
        fun(1, 2, 3); //OK
        fun(true, false, false); //OK
        fun(); // Error: Ambiguous!
    }
}

在上面的程序中,fun() 的重载是完全正确的。但是,由于以下调用,该程序将无法编译:

fun(); // Error: Ambiguous!

根据(JLS 15.2.2),重载解析中使用了 3 个阶段:第一阶段执行重载解析,不允许装箱或拆箱转换,第二阶段执行重载解析,同时允许装箱和拆箱,第三阶段允许重载与变量组合arity 方法、装箱和拆箱。如果在这些阶段没有找到适用的方法,则会出现歧义。
上面的调用可以转换为对 fun(int ...) 或 fun(boolean ...) 的调用。两者同样有效,并且在重载解决的所有三个阶段之后都不会被解决,因为两种数据类型都不同。因此,调用本质上是模棱两可的。

另一个模棱两可的例子:
以下 fun( ) 的重载版本本质上是模棱两可的:

static void fun(int ... a) { // method body  }
static void fun(int n, int ... a) { //method body }

在这里,虽然 fun() 的参数列表不同,但编译器无法解析以下调用:

fun(1)

此调用可能会解析为 fun(int ... a) 或 fun(int n, int ... a) 方法,从而产生歧义。为了解决像上面这样的歧义错误,我们需要放弃重载并简单地使用两个不同的方法名称。