Java 如何处理可能不明确的方法调用?
How does Java handle potentially ambiguous method calls?
尝试使用具有多个参数的模棱两可的方法调用时,我注意到它通常并不像我预期的那样模棱两可,这导致了一些我不太理解的奇怪行为。
例如继承结构如下:
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 中如何使用多个参数确定准模糊方法调用?
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)
。第二个只是在提供更具体的用例(签名)时覆盖前者。您可能希望将其视为覆盖前者,尽管这可能不是技术术语。
通过扩展 C
是一个 A
所以如果选择参数为 C
或 A
的调用方法,解释器将调用其中一个(因此歧义可以出现)但必须调用具有更具体参数的参数(如果可用)。这可以描述为多态性;实例的 运行 时间类型用于确定 class 并且方法调用从下降 class 向上搜索。
正如 JB Nizet 指出的那样,语言规范是这里的权威,但我也喜欢尝试。
这是因为两种测试方法都可以接受 C
的实例作为其参数,因为 C
可以充当 B
和 A
通读 choosing the most specific method 上的 Java 语言规范章节,需要注意以下陈述:
One applicable method m1 is more specific than another applicable
method m2, for an invocation with argument expressions e1, ..., ek,
if ...
...m2 is not generic, and m1 and m2 are applicable by strict or
loose invocation, and where m1 has formal parameter types S1, ..., Sn
and m2 has formal parameter types T1, ..., Tn, the type Si is more
specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).
这基本上意味着给定两个可以匹配方法调用的(非可变参数)方法签名,编译器将选择更具体的方法,并且更具体的方法是每个参数比(即与另一个签名中的相应参数相同 class 或子 class (为简单起见,此处忽略泛型)。
因此,例如当您有 (A,A,B) 和 (A,A,C) 时,后者更具体,因为 A = A 并且 C 是 B 的子class,所以选择是明确无误的。
但是当你有 (A,A,B) 和 (A,C,A) 时,前者不能更具体,因为 C 是 A 的子class,但后者可以也不要更具体,因为 B 是 A 的子class。因此,含糊不清。
我试着向您解释问题所在。
例如,我有一个 class,其中包含三种使用不同参数计算两个数相乘的方法。
这些方法是:
- 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)。
这是您在使用重载时可以发现的问题。
希望对你有所帮助!
尝试使用具有多个参数的模棱两可的方法调用时,我注意到它通常并不像我预期的那样模棱两可,这导致了一些我不太理解的奇怪行为。
例如继承结构如下:
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 中如何使用多个参数确定准模糊方法调用?
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)
。第二个只是在提供更具体的用例(签名)时覆盖前者。您可能希望将其视为覆盖前者,尽管这可能不是技术术语。
通过扩展 C
是一个 A
所以如果选择参数为 C
或 A
的调用方法,解释器将调用其中一个(因此歧义可以出现)但必须调用具有更具体参数的参数(如果可用)。这可以描述为多态性;实例的 运行 时间类型用于确定 class 并且方法调用从下降 class 向上搜索。
正如 JB Nizet 指出的那样,语言规范是这里的权威,但我也喜欢尝试。
这是因为两种测试方法都可以接受 C
的实例作为其参数,因为 C
可以充当 B
和 A
通读 choosing the most specific method 上的 Java 语言规范章节,需要注意以下陈述:
One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if ...
...m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).
这基本上意味着给定两个可以匹配方法调用的(非可变参数)方法签名,编译器将选择更具体的方法,并且更具体的方法是每个参数比(即与另一个签名中的相应参数相同 class 或子 class (为简单起见,此处忽略泛型)。
因此,例如当您有 (A,A,B) 和 (A,A,C) 时,后者更具体,因为 A = A 并且 C 是 B 的子class,所以选择是明确无误的。
但是当你有 (A,A,B) 和 (A,C,A) 时,前者不能更具体,因为 C 是 A 的子class,但后者可以也不要更具体,因为 B 是 A 的子class。因此,含糊不清。
我试着向您解释问题所在。
例如,我有一个 class,其中包含三种使用不同参数计算两个数相乘的方法。
这些方法是:
- 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)。
这是您在使用重载时可以发现的问题。
希望对你有所帮助!