Как создать универсальный массив в Java? - PullRequest
994 голосов
/ 09 февраля 2009

Из-за реализации обобщений Java вы не можете иметь такой код:

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;
}

Но я действительно не понимаю, что происходит.

Ответы [ 29 ]

5 голосов
/ 09 ноября 2016

Я нашел быстрый и простой способ, который работает для меня. Обратите внимание, что я использовал это только в Java JDK 8. Я не знаю, будет ли это работать с предыдущими версиями.

Хотя мы не можем создать экземпляр универсального массива с параметром определенного типа, мы можем передать уже созданный массив в конструктор универсального класса.

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

Теперь в main мы можем создать массив следующим образом:

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

Для большей гибкости с вашими массивами вы можете использовать связанный список, например. ArrayList и другие методы, найденные в классе Java.util.ArrayList.

5 голосов
/ 09 августа 2013

Смотрите также этот код:

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

Преобразует список объектов любого типа в массив того же типа.

4 голосов
/ 12 июля 2017

Вам не нужно передавать аргумент Class в конструктор. Попробуйте это.

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, 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]
4 голосов
/ 09 февраля 2009

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

3 голосов
/ 12 июня 2015

На самом деле более простой способ сделать это - создать массив объектов и привести его к нужному типу, как показано в следующем примере:

T[] array = (T[])new Object[SIZE];

где SIZE - это константа, а T - идентификатор типа

3 голосов
/ 31 августа 2012

Я сделал этот фрагмент кода, чтобы рефлексивно создать экземпляр класса, который передается для простой автоматизированной утилиты тестирования.

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

Обратите внимание на этот сегмент:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

для инициации массива, где Array.newInstance (класс массива, размер массива) . Класс может быть как примитивным (int.class), так и объектом (Integer.class).

BeanUtils является частью Spring.

3 голосов
/ 15 сентября 2017

Передача списка значений ...

public <T> T[] array(T... values) {
    return values;
}
1 голос
/ 19 октября 2016

Я нашел способ обойти эту проблему.

В строке ниже выдается общая ошибка создания массива

List<Person>[] personLists=new ArrayList<Person>()[10];

Однако, если я инкапсулирую List<Person> в отдельный класс, это будет работать.

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

Вы можете выставлять людей в классе PersonList через геттер. Строка ниже даст вам массив, который имеет List<Person> в каждом элементе. Другими словами массив List<Person>.

PersonList[] personLists=new PersonList[10];

Мне нужно было что-то подобное в коде, над которым я работал, и это то, что я сделал, чтобы заставить его работать. Пока проблем нет.

1 голос
/ 17 октября 2015

Никто другой не ответил на вопрос о том, что происходит в приведенном вами примере.

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;
}

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

С другой стороны, массивы do знают тип своего компонента во время выполнения.

Этот пример обходит проблему, заставляя код, вызывающий конструктор (который знает тип), передавать параметр, сообщающий классу требуемый тип.

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

Stack<foo> = new Stack<foo>(foo.class,50)

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

Array.newInstance(clazz, capacity);

Наконец, у нас есть приведение типа, потому что компилятор не может знать, что массив, возвращаемый Array#newInstance(), является правильным типом (даже если мы знаем).

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

1 голос
/ 15 сентября 2013

Принудительное приведение, предложенное другими людьми, не сработало для меня, за исключением исключения незаконного приведения.

Однако это неявное приведение работало нормально:

Item<K>[] array = new Item[SIZE];

где Item - это класс I, который содержит член:

private K value;

Таким образом, вы получаете массив типа K (если элемент имеет только значение) или любой универсальный тип, который вы хотите определить в классе Item.

...