Как создать универсальный массив?
Я не понимаю связи между универсальными файлами и массивами.
Я могу создать ссылку на массив с универсальным типом:
private E[] elements; //GOOD
Но не удается создать объект array с универсальным типом:
elements = new E[10]; //ERROR
Но это работает:
elements = (E[]) new Object[10]; //GOOD
Переведено автоматически
Ответ 1
Не следует путать массивы и универсальные типы. Они плохо сочетаются друг с другом. Существуют различия в том, как массивы и универсальные типы обеспечивают проверку типов. Мы говорим, что массивы являются материализованными, а дженерики - нет. В результате вы видите эти различия при работе с массивами и дженериками.
Массивы ковариантны, дженерики - нет:
Что это значит? Вы, должно быть, уже знаете, что допустимо следующее назначение:
Object[] arr = new String[10];
В принципе, Object[]
это супертип String[]
, потому что Object
это супертип String
. Это не относится к дженерикам. Итак, следующее объявление недопустимо и не будет компилироваться:
List<Object> list = new ArrayList<String>(); // Will not compile.
Причина в том, что универсальные массивы инвариантны.
Принудительная проверка типа:
В Java были введены универсальные типы для обеспечения более строгой проверки типов во время компиляции. Таким образом, универсальные типы не содержат никакой информации о типе во время выполнения из-за удаления типа. Итак, a List<String>
имеет статический тип List<String>
, но динамический тип List
.
Однако массивы содержат информацию о типе компонента во время выполнения. Во время выполнения массивы используют проверку хранилища массивов, чтобы проверить, вставляете ли вы элементы, совместимые с фактическим типом массива. Итак, следующий код:
Object[] arr = new String[10];
arr[0] = new Integer(10);
скомпилируется нормально, но завершится сбоем во время выполнения в результате ArrayStoreCheck . С generics это невозможно, поскольку компилятор попытается предотвратить исключение во время выполнения, предоставив проверку во время компиляции, избегая создания ссылки, подобной этой, как показано выше.
Итак, в чем проблема с созданием универсального массива?
Создание массива, тип компонента которого является либо параметром типа, конкретным параметризованным типом, либо ограниченным параметризованным типом с подстановочным знаком, небезопасно для типов.
Рассмотрим приведенный ниже код:
public <T> T[] getArray(int size) {
T[] arr = new T[size]; // Suppose this was allowed for the time being.
return arr;
}
Поскольку тип T
неизвестен во время выполнения, созданный массив на самом деле является Object[]
. Таким образом, приведенный выше метод во время выполнения будет выглядеть следующим образом:
public Object[] getArray(int size) {
Object[] arr = new Object[size];
return arr;
}
Теперь предположим, что вы вызываете этот метод как:
Integer[] arr = getArray(10);
Вот в чем проблема. Вы только что присвоили Object[]
ссылке на Integer[]
. Приведенный выше код будет скомпилирован нормально, но завершится ошибкой во время выполнения.
Вот почему создание универсального массива запрещено.
Почему приведение типов new Object[10]
к E[]
работает?
Теперь ваше последнее сомнение, почему приведенный ниже код работает:
E[] elements = (E[]) new Object[10];
Приведенный выше код имеет те же последствия, что и объясненный выше. Если вы заметили, компилятор выдаст вам предупреждение о непроверенном приведении, поскольку вы выполняете приведение к массиву неизвестного типа компонента. Это означает, что приведение может завершиться ошибкой во время выполнения. Например, если у вас есть этот код в приведенном выше методе:
public <T> T[] getArray(int size) {
T[] arr = (T[])new Object[size];
return arr;
}
и вы вызываете invoke следующим образом:
String[] arr = getArray(10);
это приведет к сбою во время выполнения с ClassCastException . Итак, нет, этот способ не будет работать всегда.
Как насчет создания массива типа List<String>[]
?
Проблема та же. Из-за удаления типа a List<String>[]
является ничем иным, как a List[]
. Итак, если бы создание таких массивов было разрешено, давайте посмотрим, что могло бы произойти.:
List<String>[] strlistarr = new List<String>[10]; // Won't compile. but just consider it
Object[] objarr = strlistarr; // this will be fine
objarr[0] = new ArrayList<Integer>(); // This should fail but succeeds.
Теперь проверка ArrayStoreCheck в приведенном выше случае завершится успешно во время выполнения, хотя это должно было вызвать исключение ArrayStoreException. Это потому, что оба List<String>[]
и List<Integer>[]
компилируются в List[]
во время выполнения.
Итак, можем ли мы создать массив неограниченных параметризованных типов с подстановочными знаками?
ДА. Причина в том, что a List<?>
является повторяемым типом. И это имеет смысл, поскольку с типом вообще не связано. Таким образом, в результате удаления типа терять нечего. Таким образом, создание массива такого типа абсолютно типобезопасно.
List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>(); // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine
Оба приведенных выше случая хороши, потому что List<?>
это супертип для всех экземпляров универсального типа List<E>
. Таким образом, он не выдаст исключение ArrayStoreException во время выполнения. То же самое происходит и с массивом необработанных типов. Поскольку необработанные типы также являются воспроизводимыми типами, вы можете создать массив List[]
.
Итак, получается, что вы можете создать массив только из воспроизводимых типов, но не из невоспроизводимых типов. Обратите внимание, что во всех вышеперечисленных случаях объявление массива выполняется нормально, проблемы возникают при создании массива с помощью new
оператора. Но нет смысла объявлять массив этих ссылочных типов, поскольку они не могут указывать ни на что, кроме null
(игнорируя неограниченные типы).
Есть ли какой-либо обходной путь для E[]
?
Да, вы можете создать массив с помощью Array#newInstance()
метода:
public <E> E[] getArray(Class<E> clazz, int size) {
@SuppressWarnings("unchecked")
E[] arr = (E[]) Array.newInstance(clazz, size);
return arr;
}
Приведение к типу необходимо, потому что этот метод возвращает Object
. Но вы можете быть уверены, что это безопасное приведение. Таким образом, вы даже можете использовать @SuppressWarnings для этой переменной.
Ответ 2
Вот реализация LinkedList<T>#toArray(T[])
:
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
Короче говоря, вы могли создавать универсальные массивы только через Array.newInstance(Class, int)
где int
- размер массива.
Ответ 3
Проблема в том, что во время выполнения общий тип стирается, поэтому new E[10]
было бы эквивалентно new Object[10]
.
Это было бы опасно, потому что в массив можно было бы поместить данные, отличные от E
типа. Вот почему вам нужно явно указать, какой тип вы хотите, либо
- создаем массив объектов и преобразуем его в
E[]
массив, или - использование Array.newInstance(Class componentType, длина int) для создания реального экземпляра массива типа, переданного в
componentType
аргументе.
Ответ 4
проверено :
public Constructor(Class<E> c, int length) {
elements = (E[]) Array.newInstance(c, length);
}
или снято :
public Constructor(int s) {
elements = new Object[s];
}