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

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

32 ответа

Решение

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

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

    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[] типа, и может вызвать непредвиденные ошибки или ClassCastException S, если используется небезопасно.

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


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

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


Изменить в отношении комментариев Инго:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

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

E[] a;

a = newArray(size);

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

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

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

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

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]

Это описано в главе 5 (Общие положения) Effective Java, 2nd Edition, пункт 25...Предпочитать списки массивам

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

@SuppressWarnings({"unchecked"})

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

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

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

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

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

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

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

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

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

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

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

@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.util.ArrayList.

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

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

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

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.

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

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

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

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

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

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

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

private K value;

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

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

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

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

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

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

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

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

Array.newInstance(clazz, capacity);

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

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

Согласно внпортной синтаксис

GenSet<Integer> intSet[] = new GenSet[3];

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

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

который безопасен по типу.

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

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

Я нашел своего рода решение этой проблемы.

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

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

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

На самом деле я нашел довольно уникальное решение, чтобы обойти невозможность инициировать универсальный массив. То, что вам нужно сделать, это создать класс, который принимает переменную общего типа 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++;
}

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

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

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

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

      List<?>[] chars = new List[3];

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

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

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

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.

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

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

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

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

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

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>();
        }
    }
}
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}
Другие вопросы по тегам