Дженерики в переопределенных методах
Перешел в интересную проблему; следующий класс компилирует:
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
, но их здесь нет.)