繁体   English   中英

为什么我会在这段代码中收到歧义错误?

[英]Why do I get an ambiguity error in this code?

假设我们有这 3 个类:

class A { }
class B extends A { }

public class App {
    static void f(int i, A a) { }
    static void f(float j, B b) { }
   
    static public void main() {
        int i = 0;
        B b = new B();
        App.f(i, b);
    }
}

这会产生错误:

App.java:11: error: reference to f is ambiguous
        App.f(i, b);
           ^
  both method f(int,A) in App and method f(float,B) in App match
1 error

为什么它不选择f(int, A)类型,因为i是 integer?

之所以模棱两可,原因有二:

  • 两种重载都适用,并且;
  • 两个重载都不比另一个更具体

请注意, f(int, A)重载和f(float, B)重载都可以使用参数(i, b)调用,因为存在从intfloat的隐式转换,以及从B到的隐式转换A

当有不止一种适用的方法时会发生什么? Java应该选择最具体的方法。 这在语言规范的§15.12.2.5中有描述。 事实证明,这些重载中的一个并非比另一个更具体。

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

  • m2 是通用的 [...]

  • m2 不是泛型的,m1 和 m2 可以通过严格或松散调用来应用,其中 m1 有形参类型 S1,...,Sn,m2 有形参类型 T1,...,Tn,类型 Si 更多对于所有 i (1 ≤ i ≤ n, n = k),参数 ei 比 Ti 更具体。

  • m2 不是通用的,并且 m1 和 m2 可通过变量 arity 调用应用 [...]

只有第二点适用于f的两个重载。 为了使其中一个重载比另一个更具体,一个重载的每个参数类型都必须比另一个重载中的相应参数类型更具体。

对于任何表达式,如果 S <: T ( §4.10 ),类型 S 比类型 T 更具体。

注意“<:”是子类型关系。 B显然是A的子类型。 float实际上是int的超类型(不是子类型!)。 这可以从§4.10.1中列出的直接子类型关系推导出来。 因此,没有一个重载比另一个更具体。

语言规范继续讨论最大限度地具体的方法,这并不真正适用于f这里。 最后,它说:

否则,方法调用不明确,出现编译时错误。

更多示例

static void f(int x) {}
static void f(float x) {}

当用int调用时并不模棱两可,因为int重载更具体。

static void f(int x, B a) {}
static void f(float x, A a) {}

当使用参数类型(int, A)调用时,它并没有歧义,因为(int, B)重载更具体。

static void f(int x, A a) {}
static void f(float x, A a) {}

当使用参数类型(int, A)调用时,它并没有歧义,因为(int, A)重载更具体。 请注意,子类型关系是自反的(即AA的子类型)。

请注意,在 java 中调用重载方法:

原始类型的优先顺序:

相同类型 > 自动加宽 > 拳击 > 向上投射(父类) > Super Class

参考类型的优先顺序:

相同类型 > Upcasting(父类) > Super Class > 拆箱

解释:

    // you have here a method who accept an int = same Type 
    //  f(int i, A a) { } and this method can accept the other parameter 
    //  because B is a subclass of A
    int i = 0;
    // But You have a method who accept a B reference = same Type 
    // f(float j, B b) { } and this method can accept the other parameter 
    // because float is actually a supertype of int as mentionned by Sweeper
    B b = new B();

 // So calling the method with an int and a B reference 
 // will confuse the compiler because both of the two methods
 // can accept the other parameter
    App.f(i, b);

两件事情:

  • 类型扩展(第一个参数)
  • Inheritance(第二个参数)

类型扩大float是比int更“宽”的类型,因此传递的int值可以很容易地打包到更大的float框中。 如果我们想将float “打包”到int中,它将不起作用,因为我们可能会丢失浮点。 在提供的情况下,拥有一个int我们可以潜在地将两个函数都传递给它。

fFloat(float f){}
int intValue = 221;
fFloat(intValue); // inside f, intValue is treated as 221.0

fInt(int i){}
float floatVal = 221.221;
fInt(floatVal); // what should compiler do with remaining .221 part?

Inheritance : A is a base class, while B is one of subclasses of A. So, when we declare method parameter as a base class, we can provide there both base class and its instance (but we will be able to use B b instance在f(int i, A a)内部,类似于A类型。因此,我们也可以将B b = new B();传递给这两种方法。

class A {
public void f() {}
}

class B extends A {
public void g() {}
}

fA(A a){}
B bInst = new B();
fA(bInst); // you can refer only to f() method, not g()

fB(B b){}
A aInst = new A();
fB(aInst); // you cannot do this, as subclass can have some additional
// stuff, that superclass does not have

//but even
A aInstBImpl = new B();
fB(aInstBImpl); // will not work without explicit casting as it is
// kind of A instance with B implementation - here polymorphism comes into action

由于所有 2 个参数(从 2 个可能)都可以适用于这两种方法,而无需任何明确的“采取行动”,因此会出现歧义错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM