The reference to the method is ambiguous, with generics, odd behavior

Consider the following scenario:

class C { void m(Class<?> c1, Class<?> c2) {} <S, U extends S> void m(S s, U u) {} } class x {{ final Class<Integer> cInteger = Integer.class; final Class<?> cSomething = null; final C c = new C(); cm(cInteger, cInteger); cm(cSomething, cSomething); // * }} 

Using Oracle javac version 1.7.0_01 (as well as 1.7.0, as well as the OpenJDK Java 7 and Java 6 compilers), I get an error message in the line marked // * :

error: the reference to m is ambiguous, and the method m (class, class) in C and the method m (S, U) in C correspond

I can’t understand why this is happening: the compiler can indicate which method is called when the static parameter type is Class<Integer> , but it has problems with Class<?> .

The IntelliJ code analysis says that this is normal, as well as the JRockit compiler (or is it Sun Java 6).

So, it’s obvious that there is a mistake here, either in those programs that say it’s right, or in those who say that it’s wrong.

Note that if I remove the restriction U (that is, if I declare m as <S, U> void M(S s, U u) {} ), it will compile without errors. In addition, a call with a raw type (i.e., Class x = null; m(x, x) ) compiles well too.

So, according to the Java specification, is this code valid or invalid?

Thanks.

+4
source share
2 answers

Are you sure? My test is that the 1st m(cInteger, cInteger) fails, and the second m(cSomething, cSomething) is fine. In addition, raw m(x,x) does not compile.

(1) m(cInteger,cInteger) does not work

Both m () correspond to arguments; for m2, the output prints S=U=Class<Integer> .

Now the question is, is m1 more specific than m2? Following the procedure described in 15.12.2.5, this is so. If so, then there is no ambiguity; m1 should be chosen as the most specific method, and the call should be compiled.

However, this violates the informal requirement specified by the specification: if m1 is more specific than m2, any call processed by m1 can be transferred to m2 without a compilation type error. For example, m1 can take arguments (Class<Integer>,Class<String>) that m2 cannot (due to a lower (than ideal) output procedure).

Javak obviously adheres to an informal concept; this is probably because the formal specification has a mistake β€” it should have included the capture transformation (explained later) in the definition of a more specific relationship; then m1 is at most m2, so the call to m(cInteger,cInteger) ambiguous.

If the other compiler strictly adheres to the formal specification, it is not its fault to inherit the spec error.

(2) m(x, x) does not work

for the same reason as (1); both methods are consistent with each other, but no more specific than the others.

m1 corresponds to the conversion of the method call, which allows you to uncheck the conversion from raw Class to Class<?> . m2 after output S=U=Class

(3) m(cSomething, cSomething) compiles

This is because m1 is the only one applicable, therefore there is no ambiguity.

m2 is not applicable, let's see why.

Firstly, the argument types are not exact (Class<?>,Class<?>) - capture conversion is applied first. (Again, the specification is very obscure (see Chapter 15), but I am very sure that this is well understood in this case, the type of any expression is applied with capture conversion)

Thus, the argument types (Class<X1>,Class<X2>) , with two new type variables. There is another disgust, a more accurate conversion would be (Class<X1>,Class<X1>) , unfortunately, the capture transformation is applied twice, independently, exposing two different types.

m1 easily matches argument types. But m2 does not match, due to the type inference procedure of a less perfect type. First, the procedure gives S=Class<X1>, U=Class<X2> , after which the constraints of variables of the type where U extends S fails are checked.

(4) U bound removal

Now (1), (2), (3) all are compiled. Since without U extends S , the output goes through.

For (1) and (2), m1 is now more specific than m2, no longer ambiguous.

For (3) now corresponds to m2; but then obscured by a more specific m1

+2
source

Perhaps because if you say in a call to the let <S> method of type Class<?> , Then <U> also Class<?> And it is impossible to determine which method should be used.

 <S, U extends S> void m(S s, U u) {} //S=U=Class<?> => void m(Class<?> s, Class<?> u) {} //same signature 

If you say that <S> and <U> are of type Class<Integer> , then this should be the second method with generics <S> and <U> .

 <S, U extend S> void m(S s, U u) {} //S=U=Class<Integer> => void m(Class<Integer> s, Class<Integer> u) {} //Ok 

Problem

<S,U> declaration indicates that you intend to use two different unrelated types.

0
source

All Articles