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

How can I create a generic array in Java?

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

Из-за реализации Java generics у вас не может быть подобного кода:

public class GenSet<E> {
private E a[];

public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // Error: generic array creation
}
}

Как я могу реализовать это, сохраняя безопасность типов?

Я видел решение на форумах Java, которое выглядит следующим образом:

import java.lang.reflect.Array;

class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}

private final T[] array;
}

Что происходит?

Переведено автоматически
Ответ 1

В ответ я должен задать вопрос: у вас GenSet "проверено" или "непроверено"? Что это значит?


  • Проверено: строгая типизация. GenSet явно знает, какой тип объектов он содержит (т. Е. его конструктор был явно вызван с Class<E> аргументом, и методы будут генерировать исключение, когда им передаются аргументы, отличающиеся от типа E. Смотрите Collections.checkedCollection.


    -> в этом случае вам следует написать:


    public class GenSet<E> {

    private E[] a;

    public GenSet(Class<E> c, int s) {
    // Use Array native method to create array
    // of a type only known at run time
    @SuppressWarnings("unchecked")
    final E[] a = (E[]) Array.newInstance(c, s);
    this.a = a;
    }

    E get(int i) {
    return a[i];
    }
    }

  • Непроверено: слабая типизация. Проверка типа фактически не выполняется ни для одного из объектов, передаваемых в качестве аргумента.


    -> в этом случае вам следует написать


    public class GenSet<E> {

    private Object[] a;

    public GenSet(int s) {
    a = new Object[s];
    }

    E get(int i) {
    @SuppressWarnings("unchecked")
    final E e = (E) a[i];
    return e;
    }
    }

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


    public class GenSet<E extends Foo> { // E has an upper bound of Foo

    private Foo[] a; // E erases to Foo, so use Foo[]

    public GenSet(int s) {
    a = new Foo[s];
    }

    ...
    }

Все это является результатом известной и преднамеренной слабости универсальных классов в Java: это было реализовано с использованием erasure, поэтому "универсальные" классы не знают, с каким аргументом типа они были созданы во время выполнения, и, следовательно, не могут обеспечить безопасность типов, если не реализован какой-либо явный механизм (проверка типов).

Ответ 2

Вы можете сделать это:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

Это один из предложенных способов реализации универсальной коллекции в эффективной Java; Пункт 26. Нет ошибок типа, нет необходимости повторно приводить массив. Однако это вызывает предупреждение, потому что это потенциально опасно, и его следует использовать с осторожностью. Как подробно описано в комментариях, это Object[] теперь маскируется под наш E[] тип и может вызвать непредвиденные ошибки или ClassCastException s при небезопасном использовании.

Как правило, такое поведение безопасно до тех пор, пока приведенный массив используется внутри (например, для резервного копирования структуры данных), а не возвращается или предоставляется клиентскому коду. Если вам нужно вернуть массив универсального типа в другой код, упомянутый вами класс reflection Array - правильный путь.


Стоит упомянуть, что везде, где это возможно, вам будет гораздо приятнее работать с Lists, а не с массивами, если вы используете generics. Конечно, иногда у вас нет выбора, но использование платформы collections Framework гораздо надежнее.

Ответ 3

Вот как использовать generics, чтобы получить массив именно того типа, который вы ищете, сохраняя при этом безопасность типов (в отличие от других ответов, которые либо вернут вам Object массив, либо приведут к предупреждениям во время компиляции):

import java.lang.reflect.Array;  

public class GenSet<E> {
private E[] a;

public GenSet(Class<E[]> type, int length) {
a = type.cast(Array.newInstance(type.getComponentType(), length));
}

public static void main(String[] args) {
GenSet<String> foo = new GenSet<String>(String[].class, 1);
String[] bar = foo.a;
foo.a[0] = "xyzzy";
String baz = foo.a[0];
}
}

Который компилируется без предупреждений, и, как вы можете видеть в main, для любого типа, который вы объявляете экземпляром GenSet as, вы можете назначить a массиву этого типа, и вы можете назначить элемент из a переменной этого типа, что означает, что массив и значения в массиве имеют правильный тип.

Это работает за счет использования литералов класса в качестве токенов типа среды выполнения, как описано в руководствах по Java. Литералы класса обрабатываются компилятором как экземпляры java.lang.Class. Чтобы использовать его, просто следуйте за именем класса с помощью .class. Таким образом, String.class действует как Class объект, представляющий класс String. Это также работает для интерфейсов, перечислений, массивов любой размерности (например, String[].class), примитивов (например, int.class) и ключевого слова void (т.е. void.class).

Class сам по себе является универсальным (объявлен как Class<T>, где T обозначает тип, который представляет Class объект), что означает, что тип String.class является Class<String>.

Итак, всякий раз, когда вы вызываете конструктор for GenSet, вы передаете литерал класса для первого аргумента, представляющего массив объявленного типа GenSet экземпляра (например, String[].class for GenSet<String>). Обратите внимание, что вы не сможете получить массив примитивов, поскольку примитивы нельзя использовать для переменных типа.

Внутри конструктора вызов метода cast возвращает переданный Object аргумент, приведенный к классу, представленному Class объектом, для которого был вызван метод. Вызов статического метода newInstance в java.lang.reflect.Array возвращает в виде Object массива типа, представленного Class объектом, переданным в качестве первого аргумента, и длины, указанной в int, переданном в качестве второго аргумента. Вызов метода getComponentType возвращает Class объект, представляющий тип компонента массива, представленного Class объектом, для которого был вызван метод (например, String.class для String[].class, null если Class объект не представляет массив).

Последнее предложение не совсем точное. Вызов String[].class.getComponentType() возвращает Class объект, представляющий класс String, но его тип Class<?>, не Class<String>, поэтому вы не можете сделать что-то вроде следующего.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

То же самое касается каждого метода в Class который возвращает Class объект.

Что касается комментария Иоахима Зауэра к этому ответу (у меня недостаточно репутации, чтобы прокомментировать это самому), пример с использованием приведения к T[] приведет к предупреждению, потому что компилятор не может гарантировать безопасность типов в этом случае.


Правка относительно комментариев Ingo:

public static <T> T[] newArray(Class<T[]> type, int size) {
return type.cast(Array.newInstance(type.getComponentType(), size));
}
Ответ 4

Это единственный типобезопасный ответ:

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
return Arrays.copyOf(array, length);
}
java arrays generics reflection