Почему я не могу создать массив универсального типа?
Короче говоря, это не скомпилируется:
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.
В 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*.
Связанный вопрос: