Reputation: 1904
Ran into an interesting issue; the following class compiles:
public class Test {
public static void main(String[] args) throws Exception {
A a = new A();
B b = new B();
foo(a);
foo(b);
}
private static void foo(A a) {
System.out.println("In A");
}
private static void foo(B b) {
System.out.println("In B");
}
private static class A {}
private static class B extends A {}
}
but this one fails:
public class Test {
public static void main(String[] args) throws Exception {
A<String> a = new A<>();
B b = new B();
foo(a);
foo(b);
}
private static void foo(A<String> a) {
System.out.println("In A");
}
private static void foo(B b) {
System.out.println("In B");
}
private static class A<T> {}
private static class B extends A {}
}
with this error:
Test.java:8: error: reference to foo is ambiguous, both method foo(A<String>) in Test and method foo(B) in Test match
foo(b);
^
Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error
I'd have thought that due to type erasure that these would be essentially identical. Any one know what's going on here?
Upvotes: 14
Views: 175
Reputation: 19692
Before the dawn of generics, Java had methods like
public class Collections
public void sort(List list) {...} [1]
And user code may have things like
public class MyList implements List ... [2]
MyList myList = ...;
Collections.sort(myList); [3]
When generics was added to Java, a decision was made to convert existing classes and methods to generic ones, without breaking any code using them. That is a great achievement in terms of difficulty, at the great price of leaving the language complicated and flawed.
So [1]
was generified, but [3]
must still compile as is, without having to generify [2]
.
The hack is in §15.12.2.3
Ai can be converted by method invocation conversion (§5.3) to Si
basically saying that if the argument type (Ai) is raw, then erase the parameter type (Si) too for the purpose of matching.
Back to your example, we see why foo(A<String>)
is considered applicable for foo(b)
.
However there's another question - is foo(A<String>)
applicable per [§15.12.2.2]? The answer seems to be "no" by the letter of the spec. But it may be a bug of the spec.
Upvotes: 2
Reputation: 198461
private static class B extends A {}
You're omitting the type arguments for A
here. What you probably mean is
private static class B<T> extends A<T> {}
Additionally, B b = new B()
has no <String>
argument, either; you must do
B<String> b = new B<String>();
...And, finally,
private static void foo(B b) {
System.out.println("In B");
}
should be something like
private static void foo(B<String> b) {
System.out.println("In B");
}
In general, if there's a type Foo
that has a generic argument, Foo
should basically always have a type argument. In this particular case, class B extends A
didn't have a type argument for A
, then B
needed a type argument, so you needed a type argument everywhere you mentioned B
. (The one major exception to that rule is instanceof
expressions, but you don't have any here.)
Upvotes: 1
Reputation: 328845
The cause is that you are mixing generics and raw types (B should be declared like class B<T> extends A<T>
or class B extends A<SomeType>
).
The actual reason why this is happening is buried somewhere in the JLS, section #15.12.2.7 and following - good luck to articulate it succintly ;-)
Upvotes: 3