Java: selection between overloaded constructors
Asked Answered
P

3

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.

Pennypennyaliner answered 16/8, 2016 at 16:15 Comment(2)
nothing can be this: ? extends String, string class is finalRedemptioner
@ΦXocę웃Пepeúpaツ good point. I'll fix thatPennypennyaliner
R
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).)

Rechaba answered 16/8, 2016 at 16:25 Comment(5)
That quote is what I was looking for. I guess I didn't read that far.Pennypennyaliner
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.Effusive
@JörgWMittag: I don't know at all, I'm afraid :(Rechaba
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 ?Recruit
@SagarNayak: Cast the argument to Object so that foo(String) isn't applicable.Rechaba
L
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.
Limburg answered 16/8, 2016 at 16:25 Comment(0)
H
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.

Hillock answered 16/8, 2016 at 16:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.