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



этот вопрос уже есть ответ здесь:




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

    29 ответов



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



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



private E[] elements; //GOOD


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



elements = new E[10]; //ERROR


но это работает:



elements = (E[]) new Object[10]; //GOOD
537   4  

4 ответов:

не следует путать массивы и дженерики. Они не очень хорошо ладят друг с другом. Существуют различия в том, как массивы и универсальные типы обеспечивают проверку типа. Мы говорим, что массивы овеществлены, но дженерики-нет. В результате вы видите эти различия, работающие с массивами и универсальными моделями.

массивы ковариантны, дженерики не являются:

что это значит? Вы должны уже знать, что следующее задание действительно:

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. С дженериками это невозможно, так как компилятор попытается предотвратите исключение времени выполнения, предоставив проверку времени компиляции, избегая создания ссылки, как показано выше.

Итак, в чем проблема с созданием универсального массива?

Создание массива, тип компонента которого либо a параметр типа, a конкретный параметризованный тип или ограниченный подстановочный знак параметризованный тип, составляет тип-небезопасная.

рассмотрим код как ниже:

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[]. Приведенный выше код будет компилироваться нормально, но не будет работать во время выполнения.

вот почему создание универсального массива запрещенный.

почему typecasting new Object[10] до E[] работает?

теперь ваше последнее сомнение, почему ниже код работает:

E[] elements = (E[]) new Object[10];

приведенный выше код имеет те же последствия, что и объясненные выше. Если вы заметили, компилятор будет давать вам Unchecked Cast Warning там, как вы typecasting в массив неизвестного типа компонента. Это означает, что приведение может завершиться неудачей во время выполнения. Например, если у вас есть этот код в приведенном выше метод:

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 в выше case будет успешным во время выполнения, хотя это должно было вызвать исключение 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[] массив, или
  • использование массив.newInstance (Class componentType, int length) to создайте реальный экземпляр массива типа, переданного в 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];
}

Comments

    Ничего не найдено.