Ответ 1
Нельзя смешивать массивы и дженерики. Они не идут хорошо вместе. Существуют различия в том, как массивы и общие типы обеспечивают проверку типа. Мы говорим, что массивы охарактеризованы, но дженериков нет. В результате этого вы видите, что эти различия работают с массивами и генериками.
Массивы ковариантны, дженериков нет:
Что это значит? Вы уже должны знать, что следующее присваивание действительно:
Object[] arr = new String[10];
В принципе, Object[]
является супер-типом String[]
, потому что Object
является супер-типом String
. Это не относится к дженерикам. Таким образом, следующее объявление недействительно и не будет компилироваться:
List<Object> list = new ArrayList<String>(); // Will not compile.
Признание причины, дженерики инвариантны.
Проверка типа проверки:
В Java были введены обобщения для обеспечения более сильной проверки типов во время компиляции. Таким образом, общие типы не имеют никакой информации о типе во время выполнения из-за типа стирания. Таким образом, List<String>
имеет статический тип List<String>
, но динамический тип List
.
Однако массивы несут с собой информацию типа времени выполнения типа компонента. Во время выполнения массивы используют проверку хранилища Array, чтобы проверить, вставляете ли вы элементы, совместимые с фактическим типом массива. Итак, следующий код:
Object[] arr = new String[10];
arr[0] = new Integer(10);
будет компилироваться отлично, но будет работать во время выполнения, в результате ArrayStoreCheck. С generics это невозможно, так как компилятор попытается предотвратить исключение во время выполнения, предоставив проверку времени компиляции, избегая создания ссылки вроде этого, как показано выше.
Итак, что проблема с Generic Array Creation?
Создание массива, тип компонента которого является либо параметром типа, либо конкретным параметризованным типом, либо ограниченным параметром подстановочного знака, type-unsafe.
Рассмотрим код, как показано ниже:
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;
}
и вы вызываете его так:
String[] arr = getArray(10);
это приведет к сбою во время выполнения с ClassCastException. Таким образом, этот способ не будет работать всегда.
Как насчет создания массива типа List<String>[]
?
Вопрос о том же. Из-за стирания типа a 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[]
во время выполнения.
Итак, можно ли создать массив неограниченных подстановочных параметров с параметрами?
Да. Причиной является то, что 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;
}
Typecast необходим, потому что этот метод возвращает Object
. Но вы можете быть уверены, что это безопасный бросок. Таким образом, вы можете использовать @SuppressWarnings для этой переменной.