Как создать универсальный массив в 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 907

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 класс, который вы упомянули, - это правильный путь.


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

вот как использовать дженерики, чтобы получить массив именно того типа, который вы ищете, сохраняя безопасность типов (в отличие от других ответов, которые либо вернут вам 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 так же, вы можете назначить 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 in java.lang.reflect.Array возвращается Object массив типа, представленного Class объект, переданный в качестве первого аргумента и длины указывается с помощью int передается в качестве второго аргумента. Вызов метода getComponentType возвращает a Class объект, представляющий тип компонента массива, представленного Class объект, на котором вызывается метод (например,String.class на String[].class,null если Class объект не является массивом).

это последнее предложение не совсем точно. Звоню String[].class.getComponentType() возвращает a Class объект, представляющий класс String, но и его тип Class<?>, не Class<String>, вот почему вы не можете сделать что-то вроде следующего.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

то же самое касается каждого метода в Class что возвращает

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

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

государственной Stack(Class<T> clazz,int capacity) конструктор требует, чтобы вы передали объект класса во время выполнения, что означает информацию о классе - это доступно во время выполнения для кода, который в нем нуждается. А то 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...
}

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

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

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

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-это класс, который я определил, содержащий член:

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

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

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

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

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

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

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

Array.newInstance(clazz, capacity);

наконец-то у нас есть приведение типа, потому что компилятор не может знать, что массив, возвращаемый 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: возможно, альтернативным способом создания такого массива, если требуемый размер был известен и мал, было бы просто ввести необходимое количество "нулевых"s в команду zeroArray?

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

вы можете использовать приведение:

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

и добавить функцию можно добавить так:

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