Как создать общий массив в 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;
}

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

29 ответов


Я должен задать вопрос в ответ: является ли ваш 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];
        }
    }
    
  • Unchecked: слабая типизация. Проверка типа фактически не выполняется ни для одного из объектов, переданных в качестве аргумента.

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

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

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

    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: она была реализована с использованием стирания, поэтому "общие" классы не знают, с каким аргументом типа они были созданы во время выполнения, и поэтому не могут обеспечить безопасность типов, если не реализован какой-либо явный механизм (проверка типа).


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

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

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

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


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


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

import java.lang.reflect.Array;  

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

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.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 означает тип, который


Это единственный ответ, который является типом safe

E[] a;

a = newArray(size);

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

продлить на несколько размеров, просто добавьте []'S и параметров измерения newInstance() (T - Это параметр типа cls это Class<T>, d1 через d5 - целые числа):

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

посмотреть Array.newInstance() для сведения.


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

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

например, это используется <A> A[] Stream.toArray(IntFunction<A[]>).

этой мог бы также можно сделать pre-Java 8 с использованием анонимных классов, но это более громоздко.


это описано в главе 5 (дженерики)эффективная Java, 2-е издание, пункт 25...предпочитают списки массивам

ваш код будет работать, хотя он будет генерировать непроверенное предупреждение (которое вы можете подавить со следующей аннотацией:

@SuppressWarnings({"unchecked"})

однако, вероятно, было бы лучше использовать список вместо массива.

есть интересное обсуждение этой ошибки / функции на проект OpenJDK сайт.


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

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

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


Привет хотя нить мертв, я хотел бы обратить ваше внимание на это:

Generics используется для проверки типов во время компиляции:

  • поэтому цель состоит в том, чтобы проверить, что приходит то, что вам нужно.
  • то, что вы возвращаете, это то, что нужно потребителю.
  • проверить это:

enter image description here

Не беспокойтесь о типовых предупреждениях, когда вы пишете общий класс. Беспокойтесь, когда вы использующий ее.


Как насчет этого решения?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

это работает и выглядит слишком просто, чтобы быть правдой. Есть ли недостаток?


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

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

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


Я нашел быстрый и простой способ, который работает для меня. Обратите внимание, что я использовал это только на 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.утиль.класс ArrayList.


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


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

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

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

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


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

static 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]

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

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

здесь SIZE является постоянным и T - это идентификатор типа


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

public <T> T[] array(T... values) {
    return values;
}

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

однако этот неявный бросок работал нормально:

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

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

private K value;

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


никто не ответил на вопрос о том, что происходит в пример вы выложили.

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

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

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


Я нашел работу вокруг этой проблемы.

строка ниже выдает ошибку создания универсального массива

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

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


вы можете создать массив объектов и привести его к E везде. Да, это не очень чистый способ сделать это, но он должен по крайней мере работать.


попробуйте это.

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

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

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

может быть, не связано с этим вопросом, но пока я получал "generic array creation " ошибка при использовании

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

я узнаю следующие работы (и работал на меня) с @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

Мне интересно, будет ли этот код создавать эффективный универсальный массив?

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

Edit: возможно, альтернативным способом создания такого массива, если требуемый размер был известен и мал, было бы просто ввести необходимое количество "null"s в команду zeroArray?

хотя, очевидно, это не так универсально, как использование кода createArray.


вы можете использовать cast:

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

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

Я действительно нашел довольно уникальное решение для обхода невозможности инициировать общий массив. Что вам нужно сделать, это создать класс, который принимает универсальную переменную T так:

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

а затем в вашем классе массива просто начните так:

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

начиная с new Generic Invoker[] вызовет проблему с непроверенным, но на самом деле не должно быть никаких проблем.

чтобы получить из массива, вы должны вызвать массив[i].переменной Итак:

public T get(int index){
    return array[index].variable;
}

остальное, например изменение размера массива, можно сделать с помощью массивов.копия() вот так:

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

и функция add может быть добавлена следующим образом:

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}

private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

создание универсального массива запрещено в java, но вы можете сделать это как

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}