简体   繁体   中英

Why do I get an ambiguity error in this code?

Let's say we have these 3 classes:

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);
    }
}

This produces the error:

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

Why does it not choose the type f(int, A) since i is an integer?

It is ambiguous because of two reasons:

  • both overloads are applicable , and;
  • neither overload is more specific than the other

Notice that both the f(int, A) overload and the f(float, B) overload can be called with the parameters (i, b) , since there is an implicit conversion from int to float , and an implicit conversion from B to A .

What happens when there are more than one applicable method? Java is supposed to choose the most specific method. This is described in §15.12.2.5 of the language spec. It turns out that it is not the case that one of these overloads are more specific than the other.

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

  • m2 is generic [...]

  • 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).

  • m2 is not generic, and m1 and m2 are applicable by variable arity invocation [...]

Only the second point applies to the two overloads of f . For one of the overloads to be more specific than the other, every parameter type of one overload has to be more specific than the corresponding parameter type in the other overload.

A type S is more specific than a type T for any expression if S <: T ( §4.10 ).

Note that"<:" is the subtyping relationship. B is clearly a subtype of A . float is actually a supertype (not subtype!) of int . This can be derived from the direct subtyping relations listed in §4.10.1 . Therefore, neither of the overloads is more specific than the other.

The language spec goes on to talk about maximally specific methods, which doesn't really apply to f here. Finally, it says:

Otherwise, the method invocation is ambiguous, and a compile-time error occurs.

More Examples

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

when called with an int are not ambiguous because the int overload is more specific.

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

when called with argument types (int, A) are not ambiguous because the (int, B) overload is more specific.

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

when called with argument types (int, A) are not ambiguous because the (int, A) overload is more specific. Note that the subtyping relationship is reflexive (ie A is a subtype of A ).

Note that for calling an overloaded method in java:

Priority Order for Primitive types:

Same type > Auto Widening > Boxing > Upcasting(Parent Class) > Super Class

Priority Order for Reference types:

Same type > Upcasting(Parent Class) > Super Class > Unboxing

Explanation:

    // 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);

Two things:

  • Type widening (1st parameter)
  • Inheritance (2nd parameter)

Type widening : float is "wider" type than int therefore passed int value can be easily packed into bigger float box. It will not work in case, when we would like to "pack" float into int , as we can potentially lost floating point. In provided case, having an int we can potentially pass it two both functions.

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 inside f(int i, A a) like type A . Hence, we can pass B b = new B(); to both methods as well.

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

Since all 2 parameters (from 2 possible) can suit to both methods without any explicit "action taken", the ambiguity error arises.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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