Как создать универсальный массив?
Я не понимаю связи между дженериками и массивами.
Я могу создать ссылку на массив с универсальным типом:
private E[] elements; //GOOD
Но не могу создать объект массива с универсальным типом:
elements = new E[10]; //ERROR
Но это работает:
elements = (E[]) new Object[10]; //GOOD
4 ответа
Вы не должны смешивать массивы и генерики. Они не идут хорошо вместе. Существуют различия в том, как массивы и универсальные типы обеспечивают проверку типов. Мы говорим, что массивы ограничены, а дженерики - нет. В результате вы увидите, что эти различия работают с массивами и обобщенными типами.
Массивы ковариантны, а Generics не являются:
Что это значит? Теперь вы должны знать, что следующее назначение действительно:
Object[] arr = new String[10];
В основном, Object[]
это супер тип String[]
, так как Object
это супер тип String
, Это не так с дженериками. Итак, следующее объявление недопустимо и не будет компилироваться:
List<Object> list = new ArrayList<String>(); // Will not compile.
Причина в том, что дженерики инвариантны.
Проверка принудительного типа:
Обобщения были введены в Java для обеспечения более строгой проверки типов во время компиляции. Таким образом, универсальные типы не имеют никакой информации о типах во время выполнения из-за стирания типов. Итак, List<String>
имеет статический тип List<String>
но динамический тип List
,
Однако массивы несут с собой информацию о типе среды выполнения типа компонента. Во время выполнения массивы используют проверку хранилища массивов, чтобы проверить, вставляются ли элементы, совместимые с фактическим типом массива. Итак, следующий код:
Object[] arr = new String[10];
arr[0] = new Integer(10);
скомпилируется нормально, но завершится с ошибкой во время выполнения в результате ArrayStoreCheck. При использовании обобщений это невозможно, поскольку компилятор попытается предотвратить исключение времени выполнения, предоставив проверку времени компиляции, избегая создания ссылки, подобной этой, как показано выше.
Итак, в чем проблема с созданием универсального массива?
Создание массива, тип компонента которого является либо параметром типа, либо конкретным параметризованным типом, либо параметризованным типом с ограниченным подстановочным знаком,небезопасно.
Рассмотрим код как показано ниже:
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[]
, Приведенный выше код будет хорошо скомпилирован, но потерпит неудачу во время выполнения.
Вот почему создание универсального массива запрещено.
Почему Typecastingnew Object[10]
вE[]
работает?
Теперь ваше последнее сомнение, почему работает приведенный ниже код:
E[] elements = (E[]) new Object[10];
Приведенный выше код имеет те же последствия, что и объясненные выше. Если вы заметите, компилятор выдаст вампредупреждение Unchecked Cast, так как вы вписываете тип в массив неизвестного типа компонента. Это означает, что приведение может потерпеть неудачу во время выполнения. Например, если у вас есть этот код в вышеуказанном методе:
public <T> T[] getArray(int size) {
T[] arr = (T[])new Object[size];
return arr;
}
и вы вызываете вызовите это так:
String[] arr = getArray(10);
это не удастся во время выполнения с ClassCastException. Так вот, ни один такой способ не будет работать всегда.
Как насчет создания массива типаList<String>[]
?
Вопрос тот же. Из-за стирания типа,List<String>[]
это ничего, кроме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[]
во время выполнения.
Так можем ли мы создать массив неограниченных подстановочных знаков параметризованных типов?
Да. Причина в том, чтоList<?>
тип reifiable. И это имеет смысл, так как нет никакого связанного типа. Так что нечего терять в результате стирания типа. Таким образом, совершенно безопасно создавать типы такого типа.
List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>(); // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine
Оба приведенных выше случая в порядке, потому что List<?>
супер тип всех экземпляров универсального типаList<E>
, Таким образом, он не будет выдавать ArrayStoreException во время выполнения. Тот же случай с массивом необработанных типов. Поскольку необработанные типы также являются типами reifiable, вы можете создать массивList[]
,
Итак, похоже, что вы можете создать только массив типов reifiable, но не типов без reifiable. Обратите внимание, что во всех вышеописанных случаях объявление массива нормально, это создание массива с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;
}
Typecast необходим, потому что этот метод возвращает Object
, Но вы можете быть уверены, что это безопасный актерский состав. Таким образом, вы даже можете использовать @SuppressWarnings для этой переменной.
Проблема в том, что во время выполнения универсальный тип стирается так new E[10]
будет эквивалентно new Object[10]
,
Это было бы опасно, потому что было бы возможно поместить в массив другие данные, чем E
тип. Вот почему вы должны явно сказать, что тип, который вы хотите, либо
- создание массива объектов и приведение его к
E[]
массив или - использование Array.newInstance(Class componentType, int length) для создания реального экземпляра массива типа, переданного в
componentType
argiment.
Вот реализация 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
это размер массива.
Проверил:
public Constructor(Class<E> c, int length) {
elements = (E[]) Array.newInstance(c, length);
}
или не проверено:
public Constructor(int s) {
elements = new Object[s];
}