Как создать обобщённый массив в Java?
Описание проблемы:
Из-за реализации обобщений в Java невозможно создать массив обобщенного типа напрямую, как показано в нижеследующем коде:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // Ошибка: создание массива с обобщенным типом
}
}
Как я могу реализовать это, сохраняя безопасность типов?
Я видел решение на форумах 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;
}
Что здесь происходит?
5 ответ(ов)
Вы можете сделать это:
E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
Это один из рекомендуемых способов реализации обобщенной коллекции в книге Effective Java; Item 26. Нет ошибок типа, и не нужно постоянно приводить массив к нужному типу. Однако это вызывает предупреждение, так как может быть потенциально опасным, и его следует использовать с осторожностью. Как указано в комментариях, этот Object[]
теперь маскируется под наш тип E[]
, и может привести к неожиданным ошибкам или ClassCastException
, если использовать его небезопасно.
Как правило, это поведение является безопасным, если приводимый массив используется только внутренне (например, для реализации структуры данных) и не возвращается или не выставляется в код клиента. Если вам нужно вернуть массив обобщенного типа другим частям кода, то класс Array
, работающий с рефлексией, который вы упомянули, будет правильным решением.
Стоит упомянуть, что, где это возможно, вам будет гораздо удобнее работать со List
, а не с массивами, если вы используете обобщения. Конечно, иногда у вас нет выбора, но использование фреймворка коллекций гораздо более надежно.
Вот единственно правильный с точки зрения безопасности типов ответ:
E[] a;
a = newArray(size);
@SafeVarargs
static <E> E[] newArray(int length, E... array) {
return Arrays.copyOf(array, length);
}
Этот метод позволяет создать массив элементов типа E
, при этом предотвращая потенциальные проблемы с приведением типов, которые могут возникнуть при использовании обычных массивов. С помощью аннотации @SafeVarargs
мы сообщаем компилятору, что этот метод безопасен для использования с переменным числом аргументов.
Вы можете не передавать аргумент типа Class
в конструктор. Попробуйте следующий код:
public class GenSet<T> {
private final T[] array;
@SafeVarargs
public GenSet(int capacity, T... dummy) {
if (dummy.length > 0)
throw new IllegalArgumentException(
"Не предоставляйте значения для аргумента dummy.");
this.array = Arrays.copyOf(dummy, capacity);
}
@Override
public String toString() {
return "GenSet of " + array.getClass().getComponentType().getName()
+ "[" + array.length + "]";
}
}
А также:
GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));
Результат будет следующим:
GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]
Таким образом, вы можете создать обобщенный класс без необходимости передавать класс в конструктор. Обратите внимание, что массив dummy
используется только для создания массива нужного типа, но сам по себе не должен содержать никаких значений.
Java Generics работают тем образом, что проверка типов осуществляется на этапе компиляции, а в скомпилированные файлы вставляются соответствующие приведения типов. Однако при этом происходит стирание типов, что позволяет использовать обобщенные библиотеки в коде, который не поддерживает дженерики (это было преднамеренное решение), но из-за этого вы не сможете обычно определить, какой именно тип используется во время выполнения.
Общий конструктор Stack(Class<T> clazz, int capacity)
требует, чтобы вы передали объект Class
во время выполнения, что означает, что информация о классе доступна во время выполнения для кода, которому она необходима. Формат Class<T>
гарантирует, что компилятор проверит, что переданный объект Class
именно соответствует классу типа T. Это не подкласс T, и не суперкласс T, а именно T.
Это, в свою очередь, позволяет вам создать массив объектов соответствующего типа в вашем конструкторе, что значит, что типы объектов, которые вы храните в вашей коллекции, будут проверяться в момент их добавления в коллекцию.
Этот подход действительно выглядит простым и работоспособным, но есть несколько важных моментов, на которые стоит обратить внимание.
Тип безопасности: Использование
@SafeVarargs
указывает компилятору, что вы уверены в типовой безопасности вариадических аргументов. Однако необходимо помнить, что если вы попытаетесь создать массивT[]
, который не является типаObject
, это может привести к проблемам с типами в рантайме.Проблема с массивами: В Java массивы являются объектами и их тип является частью их структуры. Например, вы не сможете создать массив примитивных типов через этот метод, если передать в него примитивные значения.
Потенциальные ошибки с коллекциями: Если вы хотите использовать этот метод для создания массива из коллекции, это может привести к проблемам, так как вы не сможете гарантировать, что все элементы будут одного типа.
Таким образом, данный метод полезен и удобен в случаях, когда вы уверены в типах передаваемых элементов, но обращайте внимание на высокие риски, связанные с безопасностью типов и возможные исключения во время выполнения.
Как объединить два массива в Java?
Преобразование 'ArrayList<String>' в 'String[]' в Java
Разница между <? super T> и <? extends T> в Java
Что такое PECS (Producer Extends Consumer Super)?
Как получить экземпляр класса обобщенного типа T?