【问题标题】:Ambiguous varargs methods模棱两可的可变参数方法
【发布时间】:2015-03-08 14:09:06
【问题描述】:

这是一个无法编译的代码示例:

public class Test {
    public static void main(String[] args) {
        method(1);
    }

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

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

有人能告诉我这些方法模棱两可的原因吗?提前谢谢你。

【问题讨论】:

  • 如果我打电话给method(1, 2),它应该打电话给哪个?如果是int方法,我怎么调用Integer方法?
  • method(int) 和 method(Integer) 也可以这样说,但编译器可以选择更优选的方法,这就是我问这个问题的原因。
  • Np,它不能。因为 JLS 明确指出调用了 最具体的方法。所以在这种情况下method(1) 会调用int 方法,method((Integer) 1) 会调用Integer 方法。关键是你有可变参数。
  • 但我还是无法理解原因,因为我可以通过 method(1,2,3) 和 method(new Ineger(1), new Inetger(2), new Inetger(3)) 所以我可以调用这两种方法。但我不能....
  • 为了更具体地回答,您需要将导致编译器错误的确切代码发布为 SSCCE 和完整的编译器错误。错误在于方法 invocation 而不是 declaration

标签: java methods variadic-functions


【解决方案1】:

重载解析中使用了 3 个阶段 (JLS 15.2.2):

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

  2. 第二阶段(第 15.12.2.3 节)执行重载解决方案,同时允许装箱和拆箱,但仍排除使用可变参数方法调用。如果在此阶段没有找到适用的方法,则处理继续到第三阶段。

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

在您的示例中,这两种方法都是可变数量方法,因此适用第三阶段。

现在,由于我们有两种方法可供选择,我们寻找更具体的方法。

JLS 15.12.2.5. Choosing the Most Specific Method 说:

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

...

一个适用的方法 m1 比另一个适用的方法 m2 更具体,用于带有参数表达式 e1、...、ek 的调用,如果以下任何一个为真:

...

m2不是通用的,m1和m2是通过变量参数调用来应用的,其中m1的前k个可变参数类型为S1,...,Sk,m2的前k个可变参数类型为T1, ...,Tk,对于所有 i (1 ≤ i ≤ k) 的参数 ei,类型 Si 比 Ti 更具体。另外,如果m2有k+1个参数,那么m1的第k+1个可变参数类型是m2的第k+1个可变参数类型的子类型。

在您的情况下,您有两个可通过变量 arity 调用应用的非泛型方法(即两者都有可变参数)。为了在您调用method(1) 时选择其中一种方法,其中一种方法必须比另一种更具体。在您的情况下,每个方法只有一个参数,并且要使其中一个参数比另一个更具体,该参数的类型必须是另一个方法参数的子类型。

由于int 不是Integer 的子类型并且Integer 不是int 的子类型,因此您的方法都没有比其他方法更具体。因此出现The method method(int[]) is ambiguous for the type Test 错误。

一个可行的例子:

public static void method(Object... x) {
    System.out.println("varargs");
}

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

由于IntegerObject 的子类型,调用method(1) 时将选择第二种方法。

【讨论】:

    【解决方案2】:

    考虑方法签名

    public static void foo(int a)
    

    public static void foo(Integer a)
    

    在装箱和拆箱之前,调用foo(1) 不会有歧义。为确保与早期版本的 Java 兼容,该调用保持明确。因此,重载决议的第一阶段不允许装箱、拆箱或变量 arity 调用,这些都是同时引入的。变量 arity 调用是指通过为最后一个参数(而不是数组)传递一系列参数来调用 varargs 方法。

    但是,您的方法签名的method(1) 分辨率允许装箱和拆箱,因为这两种方法都需要变量arity 调用。由于允许拳击,因此两个签名都适用。通常,当应用两个重载时,会选择最具体的重载。但是,您的签名都不比另一个更具体(因为intInteger 都不是另一个的子类型)。因此调用method(1) 是模棱两可的。

    您可以通过传递 new int[]{1} 来编译。

    【讨论】:

    • 所以如果我理解装箱/拆箱允许使用可变参数重载而禁止重载而不使用它?
    • 不,可变参数和非可变参数都允许装箱/拆箱。只是如果您不使用可变参数,编译器将选择不需要装箱/拆箱的版本,而不是需要装箱/拆箱的版本。如果涉及可变参数,编译器不喜欢不需要装箱的版本。
    【解决方案3】:

    因为它们模棱两可的。根据 JLS,您可以进行加宽、装箱或装箱-然后加宽。在您的示例中,有 2 个方法参数可以相互装箱/拆箱。在编译时,虽然它不可见因为可变参数,这在 java 中总是不完全清楚。

    即使 Sun 建议开发人员不要重载 varargs 方法,编译器中也存在与之相关的错误 (see here)。

    【讨论】:

    • 该问题已解决。 “在您的示例中,有 2 个方法参数可以相互装箱/拆箱”——但 method(int)method(Integer) 也可以相互装箱/拆箱,但没关系。 ——
    【解决方案4】:

    int 和 Integer 的区别在于 Integer 是一个对象类型。你可以在诸如查找 int 类型的最大个数或与整数比较等情况下使用

    整数对象已经与比较方法等方法相关联:

    public static void method(int x, int y) {
        System.out.println(Integer.compare(x, y));
    }
    

    了解更多信息:http://docs.oracle.com/javase/7/docs/api/

    【讨论】:

      猜你喜欢
      • 2012-12-09
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多