Я играл с дженериками и обнаружил, что, к моему удивлению, компилируется следующий код:
class A {}
class B extends A {}
class Generic<T> {
private T instance;
public Generic(T instance) {
this.instance = instance;
}
public T get(){ return instance; }
}
public class Main {
public static void main(String[] args) {
fArray(new B[1], new Generic<A>(new A())); // <-- No error here
}
public static <T> void fArray(T[] a, Generic<? extends T> b) {
a[0] = b.get();
}
}
Я ожидал, что T
будет выведен из B
. A
не расширяет B
. Так почему же компилятор на это не жалуется?
T
, похоже, выводится из Object
, поскольку я также могу передать Generic<Object>
.
Более того, при фактическом запуске кода он выдает ArrayStoreException
в строке a[0] = b.get();
.
Я не использую исходные универсальные типы. Я чувствую, что этого исключения можно было бы избежать с помощью ошибки времени компиляции или, по крайней мере, предупреждения, если бы T
действительно предполагалось как B
.
При дальнейшем тестировании с эквивалентом List<...>
:
public static void main(String[] args) {
fList(new ArrayList<B>(), new Generic<A>(new A())); // <-- Error, as expected
}
public static <T> void fList(List<T> a, Generic<? extends T> b) {
a.add(b.get());
}
Это приводит к ошибке:
The method fList(List<T>, Generic<? extends T>) in the type Main is not applicable for the arguments (ArrayList<B>, Generic<A>)
Как и в более общем случае:
public static <T> void fList(List<? extends T> a, Generic<? extends T> b) {
a.add(b.get()); // <-- Error here
}
Компилятор правильно распознает, что первый ?
может быть дальше по иерархии наследования, чем второй ?
.
Например Если первый ?
был B
, а второй ?
был A
, то это небезопасно по типу.
Так почему же в первом примере не возникает аналогичная ошибка компилятора? Это просто недосмотр? Или есть техническое ограничение?
Единственный способ вызвать ошибку - это явно указать тип:
Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable
В ходе собственного исследования я ничего не нашел, кроме этой статьи из 2005 г. (ранее generics), в котором говорится об опасностях ковариантности массивов.
Ковариация массивов, кажется, намекает на объяснение, но я не могу его придумать.
Текущий jdk - 1.8.0.0_91
2 ответа
Рассмотрим этот пример:
class A {}
class B extends A {}
class Generic<T> {
private T instance;
public Generic(T instance) {
this.instance = instance;
}
public T get(){ return instance; }
}
public class Main {
public static void main(String[] args) {
fArray(new B[1], new Generic<A>(new A())); // <-- No error here
}
public static <T> void fArray(T[] a, Generic<? extends T> b) {
List<T> list = new ArrayList<>();
list.add(a[0]);
list.add(b.get());
System.out.println(list);
}
}
Как видите, сигнатуры, используемые для вывода параметров типа, идентичны, единственное отличие состоит в том, что fArray()
только читает элементы массива, а не записывает их, что делает вывод T -> A
совершенно оправданным во время выполнения.
И компилятор не может сказать, для чего будет использоваться ваша ссылка на массив при реализации метода.
Я ожидал, что T будет выведен из B. A не расширяет B. Так почему же компилятор не жалуется на это?
T
не выводится как B
, он выводится как A
. Поскольку B
расширяет A
, B[]
является подтипом A[]
, и поэтому вызов метода правильный.
В отличие от универсальных типов, тип элемента массива доступен во время выполнения (они переопределены ). Итак, когда вы пытаетесь сделать
a[0] = b.get();
Среда выполнения знает, что a
на самом деле является массивом B
и не может содержать A
.
Проблема здесь в том, что Java динамически расширяется. Массивы существуют с первой версии Java, в то время как дженерики были добавлены только в Java 1.5. В общем, Oracle пытается сделать новые версии Java обратно совместимыми, поэтому ошибки, сделанные в более ранних версиях (например, ковариация массива), не исправляются в новых версиях.
Похожие вопросы
Новые вопросы
java
Java — это высокоуровневый объектно-ориентированный язык программирования. Используйте этот тег, если у вас возникли проблемы с использованием или пониманием самого языка. Этот тег часто используется вместе с другими тегами для библиотек и/или фреймворков, используемых разработчиками Java.