【问题标题】:How does Java handle potentially ambiguous method calls?Java 如何处理可能不明确的方法调用?
【发布时间】:2018-07-01 14:49:08
【问题描述】:

在玩弄具有多个参数的模棱两可的方法调用时,我注意到很多时候它实际上并不是我所期望的模棱两可,这导致了一些我不太理解的奇怪行为。

例如,具有以下继承结构:

public static class A {
}

public static class B extends A {

}
public static class C extends B {

}

并运行方法test()

public static void test() {
    test(new C(), new C(), new C());
}

由于某种原因,这两种方法不明确

public static void test(A x, A xx, B xxx) {
    System.out.println("TEST 1");
}

public static void test(A x, C xx, A xxx) {
    System.out.println("TEST 2");
}

但是,在第二种方法中交换最后两个参数会使那个优先。

public static void test(A x, A xx, B xxx) {
    System.out.println("TEST 1");
}

public static void test(A x, A xx, C xxx) {
    System.out.println("TEST 2"); //No longer ambiguous, this one is called
}

有人能解释一下这种行为吗?以及一般来说,在 Java 中如何确定具有多个参数的准模糊方法调用?

【问题讨论】:

  • 我只是想指出,你所描述的不是“多重继承”,它只是一个单一的继承链。
  • @bhspencer 我的错,我删除了标签
  • 我几乎可以肯定有一个副本。只需查看方法覆盖的规则即可。
  • 它由 Java 语言规范 (docs.oracle.com/javase/specs/jls/se8/html/…) 的规则确定。它们又长又复杂。没有人知道他们全部。但这不是问题,因为没有人使用这种模棱两可的重载,因为它们很难正确处理。

标签: java inheritance ambiguous


【解决方案1】:

通读choosing the most specific method 上的 Java 语言规范章节,需要注意以下语句:

一种适用的方法 m1 比另一种适用的方法更具体 方法 m2,用于使用参数表达式 e1、...、ek、 如果...

...m2 不是通用的,m1 和 m2 可以通过 strict 或 松散的调用,并且 m1 具有形式参数类型 S1, ..., Sn 而 m2 有形参类型 T1, ..., Tn,类型 Si 更多 对于所有 i (1 ≤ i ≤ n, n = k),参数 ei 的特定于 Ti。

这基本上意味着给定两个可以匹配方法调用的(非可变参数)方法签名,编译器将选择更具体的方法,更具体的方法是每个参数都比(即与另一个签名中的相应参数相同的类或子类(为简单起见,此处忽略泛型)。

所以,例如当你有 (A,A,B) 和 (A,A,C) 时,后者更具体,因为 A = A 并且 C 是 B 的子类,所以选择是明确和明确的.

但是当你有 (A,A,B) 和 (A,C,A) 时,前者不能更具体,因为 C 是 A 的子类,但后者也不能更具体,因为B 是 A 的子类。因此,歧义。

【讨论】:

    【解决方案2】:

    这是因为这两种测试方法都可以接受C 的实例作为其参数,因为C 可以充当BA

    【讨论】:

      【解决方案3】:
      public static void test(A x, A xx, B xxx) {
          System.out.println("TEST 1");
      }
      
      public static void test(A x, C xx, A xxx) {
          System.out.println("TEST 2");
      }
      

      这里的问题是这两种方法都适用于(A, C, B)。在这种特定情况下存在歧义。

      通过您提供的明确示例,两个声明没有共同的签名:

      public static void test(A x, A xx, B xxx) {
          System.out.println("TEST 1");
      }
      
      public static void test(A x, A xx, C xxx) {
          System.out.println("TEST 2"); //No longer ambiguous, this one is called
      }
      

      第一个适用于(A, A, B),第二个适用于(A, A, C)。第二个只是在提供更具体的用例(签名)时覆盖前者。您可能希望将其视为覆盖前者,尽管这可能不是技术术语。

      通过扩展CA,因此如果选择参数为CA 的调用方法,解释器将调用其中任何一个(因此可能会出现歧义),但必须调用具有更多参数的方法特定参数(如果可用)。这可以描述为多态性;实例的运行时类型用于确定类,方法调用从下降的类向上搜索。

      正如 JB Nizet 指出的那样,语言规范是这里的权威,但我也喜欢尝试。

      【讨论】:

        【解决方案4】:

        我试图解释你会出现什么问题。

        例如,我有一个包含三个方法的类,这些方法使用不同的参数计算两个数字的乘积。

        这些方法是:

        • long mul(int x, long y)
        • long mul(long x, int y)
        • long mul(long x, long y)

        因为重载所有这些方法都是有效的并且计算相同的东西:x 和 y 相乘的结果。

        假设有两个变量作为方法的参数:

        int x;
        long y;
        
        • 如果我调用 mul(x,y) 那么我将得到 x*y 作为结果,因为 mul(int, long) 是最 这些参数的具体方法。
        • 如果我调用 mul((long) x, y) 那么我会再次得到 x*y,因为 mul(long, long) 是这些参数最具体的方法。
        • 如果我调用 mul(x, x) 我不会得到 x*x,因为语言 (Java) 无法猜测参数的最佳转换是什么。

        调用 mul(x, x) 会导致这种困境:我应该将 x 转换为 long,所以调用 mul(long ,long) 或者我应该将 y 转换为 int,所以调用 mul(int, int)。

        这是你在使用重载时会发现的问题。

        希望能帮到你!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-11-16
          • 1970-01-01
          • 2022-01-05
          • 2017-08-03
          • 1970-01-01
          • 2013-06-26
          • 2020-01-05
          • 2013-01-13
          相关资源
          最近更新 更多