Как создать общий массив в Java? - программирование

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

В связи с реализацией 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;
}

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

4b9b3361

Ответ 1

Я должен задать вопрос в ответ: ваш GenSet "checked" или "unchecked"? Что это значит?

  • Проверено: сильная типизация. 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;
        }
    }
    

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

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

Ответ 2

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

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

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

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


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

Ответ 3

Здесь, как использовать generics для получения массива точно такого типа, который вы ищете при сохранении безопасности типов (в отличие от других ответов, которые либо возвратят вам массив 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 Tutorials. Литералы классов обрабатываются компилятором как экземпляры java.lang.Class. Чтобы использовать его, просто следуйте за именем класса с помощью .class. Таким образом, String.class действует как объект Class, представляющий класс String. Это также работает для интерфейсов, перечислений, массивных массивов (например, String[].class), примитивов (например, int.class) и ключевого слова void (т.е. void.class).

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

Итак, всякий раз, когда вы вызываете конструктор для GenSet, вы передаете литерал класса для первого аргумента, представляющего массив объявленного типа экземпляра GenSet (например, String[].class для 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);
}

Ответ 5

Чтобы расширить до большего размера, просто добавьте [] и параметры измерения в 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().

Ответ 6

В Java 8 мы можем создать некое общее создание массива с использованием ссылки лямбда или метода. Это похоже на рефлексивный подход (который проходит a 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 с использованием анонимных классов, но это более громоздко.

Ответ 7

Это описано в главе 5 (Generics) Эффективная Java, 2-е издание, пункт 25... Предпочтительные списки для массивы

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

@SuppressWarnings({"unchecked"})

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

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

Ответ 8

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

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

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

Ответ 9

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

Общие сведения используются для проверки типов во время компиляции:

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

enter image description here

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

Ответ 10

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

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

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

Ответ 11

Вам не нужно передавать аргумент 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]

Ответ 12

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

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

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

Ответ 13

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

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

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

    // ...

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

    // Do whatever with the array...
}

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

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.

Ответ 14

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

Ответ 15

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

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.

Ответ 16

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

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

где SIZE является константой, а T является идентификатором типа

Ответ 17

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

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

Ответ 18

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

Однако этот неявный бросок отлично работал:

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

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

private K value;

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

Ответ 19

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

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

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

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

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

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

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

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

Array.newInstance(clazz, capacity);

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

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

Ответ 20

Я нашел для себя какую-то работу.

Строка ниже генерирует ошибку создания общего массива

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 через getter. Строка ниже даст вам массив, который имеет List<Person> в каждом элементе. Другими словами, массив List<Person>.

PersonList[] personLists=new PersonList[10];

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

Ответ 21

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

Ответ 22

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

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

Ответ 23

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

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

Ответ 24

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

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

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

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

Ответ 25

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

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

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

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

Ответ 26

Вы можете использовать трансляцию:

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

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

Ответ 27

Я действительно нашел довольно уникальное решение, чтобы обойти неспособность инициировать общий массив. Вам нужно создать класс, который принимает общую переменную 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].variable следующим образом:

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

Остальные, такие как изменение размера массива, можно сделать с помощью Arrays.copyOf() следующим образом:

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

И добавление функции может быть добавлено так:

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

Ответ 28

private E a[];
private int size;

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

Ответ 29

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

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