Kiersten Arnold
Kiersten Arnold

Reputation: 1904

Generics in overridden methods

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

Answers (3)

ZhongYu
ZhongYu

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

Louis Wasserman
Louis Wasserman

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

assylias
assylias

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

Related Questions