Почему я не могу создать массив универсального типа?

Короче говоря, это не скомпилируется:

public <A> void test() {
    A[] temp = new A[]{};
}

Это из-за проблем с обратной совместимостью или что-то фундаментальное в языковой структуре, которое этому мешает?

5 ответов

Решение

Суть в том, что класс, представляющий массив, должен знать тип компонента. Следовательно, метод объекта Class:

public Class<?> getComponentType()
Returns the Class representing the component type of an array. If this class does not represent an array class this method returns null.

Итак, когда вы попробуете:

 A[] a = new A[0];

Во время компиляции очевидно, что мы не знаем тип, так как это универсальный параметр. Во время выполнения мы не знаем тип из-за стирания типа. Таким образом, создание массива невозможно.

Думайте о вышеупомянутом утверждении как эквивалентном:

 A[] a = (A[])Array.newInstance(???, 0);

И из-за стирания типа мы не можем получить класс A во время выполнения.

Был задан вопрос, почему бы не уменьшить компилятор до Object[] или Number[] или что-то в этом роде?

Это потому, что в зависимости от типа компонента будет возвращен другой класс. Так:

 new Object[0].getClass() 
 new Integer[0].getClass()

не тот же класс. В частности, метод "getComponentType()" в классе будет возвращать разные значения.

Так что если вы уменьшите его до Object[] вместо A[], вы на самом деле не получите что-то типа A[], вы получите Object[]. Object[] не может быть преобразован в Integer[] и приведет к исключению ClassCastException.

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

В Java система типов для массивов и обобщений несовместима. Существуют две основные области расхождений: динамическая и статическая проверка типов и ковариация.

Обобщения проверяются статически: компилятор обеспечивает согласованность определений типов. "Стирание типа" было компромиссом для обеспечения обратной совместимости в JVM. После компиляции определение универсального типа больше не доступно. Например, список становится списком.

Напротив, массивы динамически проверяются по типу. Рассмотрим следующий пример:

String strings[] = {"a","b","c"};
Object simple[] = strings;
simple[0] = new Object(); // Runtime error -> java.lang.ArrayStoreException 

Ковариация - это отношение наследования контейнера на основе содержимого. Даны типы A,B и контейнер C, если B isAssignableFrom(A) => C isAssignable C.

Массивы в Java являются ковариантными, в предыдущем примере, учитывая, что Class<Object>.isAssignableFrom(Class<String>) => Object[] is assignable from String[]

Напротив, универсальные типы не являются ковариантными в Java. Используя тот же пример:

List<String> stringList = new ArrayList<String>();
List<Object> objectList = stringList; // compiler error - this is not allowed.

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

Далее читайте о сложностях и последствиях этих проблем: Массивы в Java Generics

Это не работает по той же (или почти той же) причине, что new A() не может работать: вы ожидаете, что компилятор будет знать тип среды выполнения A который он явно не может знать. Это работало бы, если бы Java Generics были похожи на шаблоны C++, где новый код генерируется для каждого экземпляра шаблона, включающего A,

Да, есть фундаментальная причина, которая сводится к стиранию типов.

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

A[] temp = new A[5];       // Assume this would compile.

Object[] objects = temp;   // This is allowed, since A extends Object.

objects[0] = new String(); // This does *not* throw an ArrayStoreException
                           // due to type erasure since the original type of A
                           // is now Object.

A t = temp[0];             // Now this seemingly innocent line would *fail*.

Связанный вопрос:

Другие вопросы по тегам