4

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();
  c.m(cInteger,  cInteger);
  c.m(cSomething,  cSomething); // *
}}

Using Oracle's javac, version 1.7.0_01 (and also 1.7.0, as well as OpenJDK's Java 7 and Java 6 compilers), I get an error on the line marked with // *:

error: reference to m is ambiguous, both method m(Class,Class) in C and method m(S,U) in C match

I cannot understand why this happens: the compiler is able to tell which method is being called when the static type of the parameter is Class<Integer>, but it has problems with Class<?>.

IntelliJ's code analysis says this is OK, as well as JRockit's (or is it Sun's Java 6) compiler.

So, there is obviously a bug here, either in those softwares that say it is right, or in those that say it is wrong.

Note that if I remove the bound of U (that is, if I declare m as <S, U> void M(S s, U u) {}, it will compile without errors. Also, a call with a raw type (ie, Class x = null; m(x, x)) compiles fine as well.

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

Thanks.

Bruno Reis
  • 37,201
  • 11
  • 119
  • 156

2 Answers2

2

Are you sure? My test is that the 1st m(cInteger, cInteger) fails, and the 2nd m(cSomething, cSomething) is ok. Also the raw m(x,x) doesn't compile.

(1) m(cInteger,cInteger) fails

Both m() match the arguments; for m2, inference yields S=U=Class<Integer>.

Now the question is, is m1 more specific than m2? Following the procedure in 15.12.2.5, it is. If that is the case, there is no ambiguity; m1 should be picked as the most specific method, and the call should compile.

However, this violates the informal requirement given by the spec: if m1 is more specific than m2, any invocation handled by m1 could be passed on to m2 without a compile-time type error. For example, m1 can accept arguments (Class<Integer>,Class<String>), which m2 can't (due to the less-than-perfect type inferrence procedure).

Javac obviously adhere to the informal notion; this is probably because the formal spec has a bug - it should have included capture conversion (explained later) in the definition of more specific relation; then m1 is not more specific than m2, therefore the call m(cInteger,cInteger) is ambiguous.

If another compiler strictly adheres to the formal spec, it's not its fault to inherit the spec's bug.

(2) m(x, x) fails

for the same reason as (1); both methods match, but neither is more specific than the other.

m1 matches through method invocation conversion, which allows unchecked conversion from raw Class to Class<?>. m2 matches after inferring S=U=Class

(3) m(cSomething, cSomething) compiles

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

m2 is not applicable, let's see why.

First, the types of the arguments are not exactly (Class<?>,Class<?>) - capture conversion is applied on them first. (Again, the spec is very unclear (see chapter 15), but I'm very certain that this is well understood to be the case; the type of any expression is applied with capture conversion)

So the arguments types are (Class<X1>,Class<X2>), with two fresh type variables. There's another screwup here, a more precise conversion would've been (Class<X1>,Class<X1>), unfortunately, capture conversion is applied twice, indepedently, yeilding two different types.

m1 easily matches the argument types. But m2 doesn't match, due to the less-than-perfect type inference procedure. The procedure first yields that S=Class<X1>, U=Class<X2>, after which, bounds of type variables are checked, where U extends S fails.

(4) removing U's bound

Now (1),(2),(3) all compile. Because without U extends S, inference passes.

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

For (3), m2 now matches; but then is shadowed by the more specific m1

irreputable
  • 44,725
  • 9
  • 65
  • 93
0

Maybe because if you say in method call let <S> be of type Class<?> then <U> is also Class<?> and It's 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 let <S> and <U> be type of Class<Integer> then it must 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 specifies that your intend is to use two different unrelated types.

viktor
  • 1,277
  • 10
  • 16