12

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

14

Описание проблемы:

Из-за реализации обобщений в 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 ответ(ов)

2

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

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

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

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


Стоит упомянуть, что, где это возможно, вам будет гораздо удобнее работать со List, а не с массивами, если вы используете обобщения. Конечно, иногда у вас нет выбора, но использование фреймворка коллекций гораздо более надежно.

0

Вот единственно правильный с точки зрения безопасности типов ответ:

E[] a;

a = newArray(size);

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

Этот метод позволяет создать массив элементов типа E, при этом предотвращая потенциальные проблемы с приведением типов, которые могут возникнуть при использовании обычных массивов. С помощью аннотации @SafeVarargs мы сообщаем компилятору, что этот метод безопасен для использования с переменным числом аргументов.

0

Вы можете не передавать аргумент типа 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 используется только для создания массива нужного типа, но сам по себе не должен содержать никаких значений.

0

Java Generics работают тем образом, что проверка типов осуществляется на этапе компиляции, а в скомпилированные файлы вставляются соответствующие приведения типов. Однако при этом происходит стирание типов, что позволяет использовать обобщенные библиотеки в коде, который не поддерживает дженерики (это было преднамеренное решение), но из-за этого вы не сможете обычно определить, какой именно тип используется во время выполнения.

Общий конструктор Stack(Class<T> clazz, int capacity) требует, чтобы вы передали объект Class во время выполнения, что означает, что информация о классе доступна во время выполнения для кода, которому она необходима. Формат Class<T> гарантирует, что компилятор проверит, что переданный объект Class именно соответствует классу типа T. Это не подкласс T, и не суперкласс T, а именно T.

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

0

Этот подход действительно выглядит простым и работоспособным, но есть несколько важных моментов, на которые стоит обратить внимание.

  1. Тип безопасности: Использование @SafeVarargs указывает компилятору, что вы уверены в типовой безопасности вариадических аргументов. Однако необходимо помнить, что если вы попытаетесь создать массив T[], который не является типа Object, это может привести к проблемам с типами в рантайме.

  2. Проблема с массивами: В Java массивы являются объектами и их тип является частью их структуры. Например, вы не сможете создать массив примитивных типов через этот метод, если передать в него примитивные значения.

  3. Потенциальные ошибки с коллекциями: Если вы хотите использовать этот метод для создания массива из коллекции, это может привести к проблемам, так как вы не сможете гарантировать, что все элементы будут одного типа.

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь