Подтвердить что ты не робот

Почему при выводе типов массивов нет java-типов?

Я играл с дженериками и обнаружил, что, к моему удивлению, следующий код компилируется:

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, так как я могу передать a 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 года (до дженериков), в котором говорится о опасности ковариации массива.

Ковариация массивов, похоже, намекает на объяснение, но я не могу думать об этом.


Текущий jdk равен 1.8.0.0_91

4b9b3361

Ответ 1

Рассмотрим следующий пример:

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 совершенно оправданным во время выполнения.

И нет возможности компилятору рассказать, для чего будет использоваться ссылка на массив в ходе реализации метода.

Ответ 2

Я ожидал бы, что T будет выведено на B. A не расширяет B. Так почему компилятор не жалуется на это?

T не считается B, предполагается, что он равен A. Поскольку B extends A, B[] является подтипом A[], и поэтому вызов метода правильный.

В отличие от generics, тип элемента массива доступен во время выполнения (они обновляются). Поэтому, когда вы пытаетесь сделать

a[0] = b.get();

среда выполнения знает, что A на самом деле является массивом B и не может содержать A.

Проблема в том, что Java расширяется динамически. Массивы существуют с первой версии Java, а generics - только в Java 1.5. В общем, Oracle пытается сделать новые версии Java обратно совместимыми, и поэтому ошибки, сделанные в более ранних версиях (например, ковариация массива), не исправлены в более новых версиях.