Вопрос-ответ

How to create a generic array? [duplicate]

Как создать универсальный массив?

Я не понимаю связи между универсальными файлами и массивами.

Я могу создать ссылку на массив с универсальным типом:

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];
}
2023-05-15 04:15 java generics