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

📅  最后修改于: 2020-03-31 06:18:40             🧑  作者: Mango

先决条件– Varargs方法重载

Varargs中的方法重载

重载允许不同的方法具有相同的名称,但签名不同,其中签名可能因输入参数的数量或输入参数的类型或两者而不同。我们可以通过采用可变长度参数的方式重载:
情况1 :仅具有Varargs参数的方法,在这种情况下,Java使用类型差异来确定要调用的重载方法。如果一个方法签名比另一个方法签名更具体,则Java选择它时不会出现错误。

//Java展示使用varargs进行重载
public class varargsDemo
{
    public static void main(String[] args)
    {
        fun();
    }
    //varargs方法,float数据类型
    static void fun(float... x)
    {
        System.out.println("float varargs");
    }
    //varargs方法,int数据类型
    static void fun(int... x)
    {
        System.out.println("int varargs");
    }
    //varargs方法,double数据类型
    static void fun(double... x)
    {
        System.out.println("double varargs");
    }
}

输出:

int varargs

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

  • double>float
  • float>long
  • long>int
  • int>char
  • int>short
  • short>byte

情况2 : 带有Varargs以及其他参数的方法在这种情况下,Java使用参数的数量和参数的类型来确定要调用的方法。
下面是重载fun()方法三次的Java程序:

// Java展示Varargs重载
class Test
{
    // 一个方法,使用varargs(integers).
    static void fun(int ... a)
    {
        System.out.print("fun(int ...): " +
                "参数的数量: " + a.length +
                " 计数: ");
        // 使用for each循环来打印a的内容
        for(int x : a)
            System.out.print(x + " ");
        System.out.println();
    }
    // 一个方法,使用varargs(booleans).
    static void fun(boolean ... a)
    {
        System.out.print("fun(boolean ...) " +
                "参数的数量: " + a.length +
                " 内容: ");
        // 用for each循环来打印a的内容
        for(boolean x : a)
            System.out.print(x + " ");
        System.out.println();
    }
    // 一个方法,使用varargs(string和integers).
    static void fun(String msg, int ... a)
    {
        System.out.print("fun(String, int ...): " +
                msg + a.length +
                " 内容: ");
        // 用for each循环来打印a的内容
        for(int x : a)
            System.out.print(x + " ");
        System.out.println();
    }
    public static void main(String args[])
    {
        // 用不同的参数调用重载之后的fun()
        fun(1, 2, 3);
        fun("测试: ", 10, 20);
        fun(true, false, false);
    }
}

输出:

fun(int ...): 参数的数量: 3 内容: 1 2 3
fun(String, int ...): 测试: 2 内容: 10 20
fun(boolean ...) 参数的数量: 3 内容: true false false

Varargs和歧义

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

// Java Varargs和歧义
class Test
{
    // 一个方法使用varargs(integers).
    static void fun(int ... a)
    {
        System.out.print("fun(int ...): " +
                "参数数量: " + a.length +
                " 内容: ");
        // 使用for each循环打印a
        for(int x : a)
            System.out.print(x + " ");
        System.out.println();
    }
    // 一个方法使用varargs(booleans).
    static void fun(boolean ... a)
    {
        System.out.print("fun(boolean ...) " +
                "参数的数量: " + a.length +
                " 内容那个: ");
        // 使用 for each打印a
        for(boolean x : a)
            System.out.print(x + " ");
        System.out.println();
    }
    public static void main(String args[])
    {
        // 调用重载的fun()
        fun(1, 2, 3); //合法
        fun(true, false, false); //合法
        fun(); // 错误: 歧义!!
    }
}

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

fun(); // 错误: 歧义!

根据(JLS 15.2.2),有3个阶段用于重载解析:第一阶段执行解析而不允许装箱或拆箱转换;第二阶段执行重载解析,同时允许装箱和拆箱;第三阶段允许重载与变量组合arity方法,装箱和拆箱。如果在这些阶段未找到适用的方法,则会产生歧义。
上面的调用可以转换为对fun(int…)或fun(boolean…)的调用。两者同等有效,并且在重载解析的所有三个阶段之后都无法解析,因为两种数据类型都不同。因此,该调用本质上是模棱两可的。
歧义的另一个示例:
fun()的以下重载版本本质上是模棱两可的:

static void fun(int ... a) { // 方法体  }
static void fun(int n, int ... a) { //方法体 }

在这里,尽管fun()的参数列表有所不同,但是编译器无法解决以下调用:

fun(1)

此调用可能解析为fun(int…a)或fun(int n,int…a)方法,从而造成歧义。为了解决上述类似的歧义错误,我们将需要放弃重载,而仅使用两个不同的方法名称。