Дженерики в переопределенных методах

Перешел в интересную проблему; следующий класс компилирует:

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 {}

}

но это не удается:

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 {}

}

с этой ошибкой:

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

Я бы подумал, что из-за стирания стилей они будут практически идентичны. Кто-нибудь знает, что здесь происходит?

Ответы

Ответ 1

До рассвета дженериков у Java были такие методы, как

public class Collections

    public void sort(List list) {...}               [1]

И код пользователя может иметь такие вещи, как

public class MyList implements List ...             [2]

MyList myList = ...;
Collections.sort(myList);                           [3]

Когда к Java добавлены обобщения, было принято решение об преобразовании существующих классов и методов в общие, не нарушая при этом какой-либо код. Это большое достижение с точки зрения сложности, по большой цене, оставляя язык сложным и ошибочным.

Итак, [1] был сгенерирован, но [3] должен компилироваться как есть, без необходимости генерировать [2].

Взлом находится в §15.12.2.3

Ai можно преобразовать путем преобразования вызова метода (§5.3) в Si

в основном говоря, что если тип аргумента (Ai) является необработанным, то также стирают тип параметра (Si) для сопоставления.

Вернемся к вашему примеру, мы видим, почему foo(A<String>) считается применимым для foo(b).

Однако существует ли другой вопрос - foo(A<String>) применимо к [§15.12.2.2]? Ответ кажется "нет" буквой спецификации. Но это может быть ошибкой спецификации.

Ответ 2

Причина в том, что вы смешиваете генерические и необработанные типы (B должен быть объявлен как class B<T> extends A<T> или class B extends A<SomeType>).

Фактическая причина, по которой это происходит, похоронена где-то в JLS, раздел № 15.12.2.7 и следующая - удачи сурово сформулировать ее; -)

Ответ 3

private static class B extends A {}

Вы опускаете аргументы типа для A здесь. Вероятно, вы имеете в виду

private static class B<T> extends A<T> {}

Кроме того, B b = new B() не имеет аргумента <String>; вы должны сделать

B<String> b = new B<String>();

... И, наконец,

private static void foo(B b) {
    System.out.println("In B");
} 

должно быть что-то вроде

private static void foo(B<String> b) {
    System.out.println("In B");
} 

В общем случае, если существует тип Foo, который имеет общий аргумент, Foo должен всегда иметь аргумент типа. В этом конкретном случае class B extends A не имел аргумента типа для A, тогда B нужен аргумент типа, поэтому вам понадобился аргумент типа везде, где вы упомянули B. (Единственное важное исключение из этого правила - выражения instanceof, но их здесь нет.)