Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous overloaded generic method with wildcard in parameter

Given the following declarations

interface Base<A> { }

interface Special<A,B> extends Base<A> { }

<T> void foo(Base<T> b) {}

<T> void foo(Special<?,T> s) {}

Why do I get a compile error for the following code:

Special<String, Integer> s = null;
foo(s); // error: reference to foo is ambiguous

BTW, the problem can be fixed by changing the declaration of the second method to

<T,X> void foo(Special<X,T> s) {}
like image 673
Cephalopod Avatar asked Mar 23 '15 13:03

Cephalopod


1 Answers

First of all, a very interesting question.

Without generics

Consider the following code:

interface NoGenericsBase { }

interface NoGenericsSpecial extends NoGenericsBase { }

interface NoGenericsSuperSpecial extends NoGenericsSpecial { }

void foo(NoGenericsBase b) {
    System.out.println("Feel tha base");
}

void foo(NoGenericsSpecial s) {
    System.out.println("Special delivery");
}

We've always been taught that the compiler picks the most specific method. And indeed, the abovementioned code will, provided you call foo((NoGenericsSuperSpecial) null), print the following:

Special delivery

So far, so good.

Generics

Now, let's test some generics behaviour:

interface Base<T> { }

interface Special<T> extends Base<T> { }

void foo(Base<? extends Number> b) {
    System.out.println("Feel tha base");
}

void foo(Special<? extends Number> s) {
    System.out.println("Special delivery");
}

public static void main(String[] args) {
    Special<Integer> v = null;
    new Main().foo(v);
}

This code compiles. The compiler finds two matches – both Base<? extends Number> as Special<? extends Number> apply –, but the compiler is able to figure out which is the most specific: it will pick void foo(Special<? extends Number>), because both captures of the unbound wildcards are equal.

But let us rewrite foo(Base<...>) method and leave the remaining untouched:

void foo(Base<? extends Integer> b) {
    System.out.println("Feel tha base");
}

Now the following error occurs:

reference to foo is ambiguous
both method foo(Base<? extends Integer>) and method foo(Special<? extends Number>) match

Before figuring out the most specific type matching, the compiler handles type variables. Apparently, the compiler cannot figure out whether <? extends Number> or <? extends Integer> applies, unregarded the type of the variable itself (Base or Special).

It seems that the variable type handling comes prior to the selection of method signatures regarding inheritance.

One (or at least I myself) should expect the compiler to pick foo(Special<? extends Number>), but this is not the case.

The reason

I do not know whether the compiler is not able to pick the most specific one regarding generics, or it's not configured to do so.

See the Java Language Specification § 18.5 or § 4.5.1 for more details.

Let me take some time to read more of generics, so maybe we can figure it out...

like image 79
MC Emperor Avatar answered Nov 06 '22 22:11

MC Emperor