35

Per this question, Java will select the "most specific" option when trying to select between ambiguous overloaded constructors. In this example:

public class Test{
    private Test(Map map){
        System.out.println("Map");
    }
    private Test(Object o){
        System.out.println("Object");
    }
    public static void main(String[] args){
        new Test(null);
    }
}

it will print

"Map"

However, I was trying to figure out exactly what "most specific" means. I assumed it meant "least ambiguous", as in "may refer to the fewest possible types." In this context, Object may be anything that isn't a primitive, while Map may only be Map or ? extends Map. Basically, I assumed that whichever class was closer to the leaf of the inheritance tree would be selected. That works when one class is a subclass of the other:

public class Test{
    private Test(A a){
        System.out.println("A");
    }
    private Test(B b){
        System.out.println("B");
    }
    public static void main(String[] args){
        new Test(null);
    }
}

class A{}

class B extends A{}

"B"

Then I came up with this:

public class Test{
    private Test(A a){
        System.out.println("A");
    }
    private Test(E e){
        System.out.println("E");
    }
    public static void main(String[] args){
        new Test(null);
    }
}

class A{}

class B extends A{}

class C{}

class D extends C{}

class E extends D{}

I would think it should print E, as E may only refer to one known type, whereas A may refer to two (A and B). But it give an ambiguous reference error.

How is it actually choosing the constructor? I read through the docs but frankly I couldn't quite follow how it determines specificity. I'm hoping for a description of exactly why it can't determine that E is more specific than A.

Community
  • 1
  • 1
ewok
  • 20,148
  • 51
  • 149
  • 254

3 Answers3

45

It's not based on the number of types that are convertible to the parameter type - it's whether any value that's valid for one overload is valid for another, due to implicit conversions.

For example, there's an implicit conversion from String to Object, but the reverse isn't true, so String is more specific than Object.

Likewise there's an implicit conversion from B to A, but the reverse isn't true, so B is more specific than A.

With A and E however, neither is more specific than the other - there no conversion from A to E, and no conversion from E to A. That's why overload resolution fails.

The relevant bit of the JLS is actually 15.12.2.5, which includes this that might make it easier for you to understand:

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.

So if you have:

void foo(String x)
void foo(Object x)

every invocation handled by foo(String) could be handled by foo(Object), but the reverse is not the case. (For example, you could call foo(new Object()) and that couldn't be handled by foo(String).)

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • That quote is what I was looking for. I guess I didn't read that far. – ewok Aug 16 '16 at 16:27
  • 3
    Do you happen to know how "complex" (choose whatever metric you want) Java's overload resolution is? E.g. C♯'s overload resolution is in NP, in fact, you can encode any 3-SAT problem in a set of overloads and have the compiler solve it for you. – Jörg W Mittag Aug 16 '16 at 22:17
  • @JörgWMittag: I don't know at all, I'm afraid :( – Jon Skeet Aug 17 '16 at 05:08
  • how can i call the least specific method ?? if i have foo(Object o); and foo(String s);. and i want to call foo(Object o). what do i do ? – Sagar Nayak Jun 30 '17 at 10:25
  • @SagarNayak: Cast the argument to `Object` so that `foo(String)` isn't applicable. – Jon Skeet Jun 30 '17 at 13:22
9

Following statement of the JSL§15.12.2.5 answers that,

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.

Case 1

  • You can pass anything in constructor which takes Object while we can not pass anything other than a Map in first constructor. So whatever I pass in Map constructor can be handled by Object constructor and that's why Test(Map map) becomes mote specific.

Case 2

  • Since B extends A, here Test(B b) constructor becomes more specific. As we can pass B in Test(A a) thanks to inheritance.

Case 3

  • In this case there is no direct conversion to depict the more specific method and it results in ambiguity.
akash
  • 22,664
  • 11
  • 59
  • 87
6

This behavior is because E is not more specific than A, as they belong to different hierarchies they cannot be compared. Thus, when you pass a null, Java cannot know which hierarchy to use.

Jordi
  • 2,055
  • 1
  • 16
  • 34