Как создать общий массив?

Я не понимаю связи между дженериками и массивами.

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

private E[] elements; //GOOD

Но не удается создать объект массива с общим типом:

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 были введены обобщения для обеспечения более сильной проверки типов во время компиляции. Таким образом, общие типы не имеют никакой информации о типе во время выполнения из-за типа стирания. Таким образом, 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 для этой переменной.

Ответ 2

Проблема заключается в том, что во время стирания общий тип стирается, поэтому new E[10] будет эквивалентен new Object[10].

Это было бы опасно, потому что можно было бы добавить в массив другие данные, кроме типа E. Вот почему вам нужно явно указать тот тип, который вы хотите, с помощью

  • создание массива объектов и передача его в массив E[] или
  • использование Array.newInstance(класс componentType, int length) для создания реального экземпляра массива типа, переданного в componentType argiment.

Ответ 3

Вот реализация 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 - размер массива.

Ответ 4

checked:

public Constructor(Class<E> c, int length) {

    elements = (E[]) Array.newInstance(c, length);
}

или непроверено:

public Constructor(int s) {
    elements = new Object[s];
}