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

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


person tatsuhirosatou    schedule 09.02.2009    source источник
comment
Вам действительно нужно использовать здесь массив? А как насчет использования Коллекции?   -  person matt b    schedule 09.02.2009
comment
Да, я также считаю, что коллекции более элегантны для решения этой проблемы. Но это для назначения класса и они обязательны :(   -  person tatsuhirosatou    schedule 09.02.2009
comment
Я не понимаю, зачем мне здесь размышлять. Грамматика Java странная: вроде new java.util.HashMap ‹String, String› [10] недействителен. new java.util.HashMap ‹long, long› (10) недействителен. new long [] [10] недействителен, new long [10] [] действителен. Из-за этого написать программу, которая может писать java-программу, сложнее, чем кажется.   -  person bronze man    schedule 30.06.2017


Ответы (31)


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

  • Проверено: строгий ввод. GenSet явно знает, какой тип объектов он содержит (т. Е. Его конструктор был явно вызван с Class<E> аргументом, и методы вызовут исключение, когда им будут переданы аргументы, не относящиеся к типу E. См. _ 5_.

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

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

person Varkhan    schedule 09.02.2009
comment
Что было бы лучшим вариантом с точки зрения производительности? Мне нужно получать элементы из этого массива довольно часто (в цикле). Так что сбор, вероятно, медленнее, но какой из этих двух самый быстрый? - person user1111929; 08.09.2012
comment
И если общий тип ограничен, резервный массив должен быть ограничивающего типа. - person Mordechai; 08.04.2013
comment
@AaronDigulla Просто чтобы уточнить, что это не присвоение, а инициализация локальной переменной. Вы не можете аннотировать выражение / утверждение. - person kennytm; 26.09.2013
comment
@Varkhan Есть ли способ изменить размер этих массивов из реализации класса. Например, если я хочу изменить размер после переполнения, например, ArrayList. Я просмотрел реализацию ArrayList, которая у них Object[] EMPTY_ELEMENTDATA = {} для хранения. Могу ли я использовать этот механизм для изменения размера, не зная типа с помощью универсальных шаблонов? - person Dhawan Gayash; 28.08.2014
comment
Для тех, кто хочет создать метод с универсальным типом (это то, что я искал), используйте это: public void <T> T[] newArray(Class<T> type, int length) { ... } - person Daniel Kvist; 13.03.2015
comment
Означает ли это, что каждый раз, когда я создаю экземпляр GenSet какого-либо типа, скажем, например, new GenSet<Float>, я должен передать Type.class (то есть new GenSet<Float>(Float.class, 10)? Нет ли способа записать его как new GenSet<Float>(10) и при этом сохранить строгую типизацию? - person gozzilli; 08.06.2015
comment
@gozzilli К сожалению, нет, потому что дженерики Java в основном, ну, подделка. Вы ничего не можете сделать, не имея объекта класса. - person Nyerguds; 06.11.2017
comment
Есть ли способ получить переменную c типа Class<E> внутри самого метода, не передавая ее в качестве параметра? - person QWERTY; 09.06.2020

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

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

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

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


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

person dimo414    schedule 27.05.2010
comment
Это не сработает, если массив рассматривается как типизированный массив любого типа, например String[] s=b; в приведенном выше методе test(). Это потому, что массив E на самом деле не является, это Object []. Это имеет значение, если вы хотите, например a List<String>[] - вы не можете использовать для этого Object[], вы должны иметь именно List[]. Вот почему вам нужно использовать создание отраженного массива Class ‹?›. - person Lawrence Dol; 11.10.2010
comment
@SoftwareMonkey Я пробовал это с List<String> как E. Запуск от имени t.genericArrayTest(Arrays.asList("Hello World")); Прекрасно работает. - person misiu_mp; 10.08.2011
comment
Угловой случай / проблема заключается в том, если вы хотите сделать, например, public E[] toArray() { return (E[])internalArray.clone(); }, когда internalArray набирается как E[], и поэтому на самом деле это Object[]. Это не удается во время выполнения с исключением типа приведения, потому что Object[] не может быть назначен массиву любого типа E. - person Lawrence Dol; 11.08.2011
comment
По сути, этот подход будет работать до тех пор, пока вы не вернете массив, не передадите его или не сохраните в каком-либо месте за пределами класса, для которого требуется массив определенного типа. Пока вы находитесь в классе, все в порядке, потому что E стирается. Это опасно, потому что, если вы попытаетесь вернуть его или что-то в этом роде, вы не получите предупреждения о том, что это небезопасно. Но если вы будете осторожны, это сработает. - person newacct; 24.09.2011
comment
Это вполне безопасно. В E[] b = (E[])new Object[1]; вы можете ясно видеть, что единственная ссылка на созданный массив - это b, а тип b - E[]. Следовательно, нет опасности, что вы случайно получите доступ к одному и тому же массиву через другую переменную другого типа. Если бы вместо этого у вас было Object[] a = new Object[1]; E[]b = (E[])a; , вам бы пришлось параноидально относиться к тому, как вы используете a. - person Aaron McDaid; 21.01.2012
comment
По крайней мере, в Java 1.6 это генерирует предупреждение: Непроверенное приведение из Object [] в T [] - person Quantum7; 24.03.2012
comment
Очень странно, я бы использовал 1.6, когда писал этот пост, но теперь я тоже вижу предупреждение. - person dimo414; 25.03.2012
comment
Гений! Это работает и в GWT, где отражение не работает. Спасибо! Кстати, добавьте @SuppressWarnings (не отмечен), чтобы избавиться от предупреждения. - person Ryan Shillington; 17.05.2012
comment
Это неверно, потому что b - это массив объектов. Вы должны просто написать Object[] b = new Object[1], все остальное обманывает вас и компилятор (возможно, заставляет его сообщить вам, если вы собирались хранить только строки внутри, когда вы не согласны). Вы можете сохранить String в массиве объектов, потому что String наследуется от Object, но вы определенно не создали массив строк. - person Blackzafiro; 18.03.2015
comment
@Blackzafiro, как я уже сказал, этот Object[] теперь маскируется под наш E[] тип - этот метод следует использовать только в том случае, если мы уверены, что массив не будет передан или использован другим образом. Для частного / внутреннего кода это достаточно безопасно. - person dimo414; 18.03.2015
comment
@ dimo414, безопасно, но только для частного / внутреннего кода? Чтобы быть ясным для не частного / внутреннего кода, извините, ребята, но это попахивает довольно грязной идиомой. (E[])(new Object[1]) - это не то, что уставший инженер в 3 часа ночи сразу же увидит подводные камни и привкус чего-то, что имеет вид Disaster Waiting to Happen®. Я бы не стал использовать его для того, что писал исключительно для себя. - person ; 03.04.2015
comment
@ tgm1024 меня устраивает, если ты не хочешь его использовать. В первую очередь вам не следует смешивать дженерики и массивы, поэтому я не думаю, что проблема действительно в этом решении. Это примерно так же безопасно, как использование необработанных типов, что, конечно, не рекомендуется, но для кода, который не раскрывает массив, обычно не является вредным. Как я уже сказал, это не идеально, но легко. Иногда такой компромисс приемлем. - person dimo414; 04.04.2015
comment
Это вызывает исключение приведения, если, например, возвращается такой массив из метода, в котором вызывающий объект ожидает определенного типа. - person Singagirl; 22.08.2016
comment
@dmitriyr да, как я уже сказал в ответе, это небезопасно для общего использования; его лучше всего использовать для внутренних структур данных, где вы контролируете доступ к массиву. Если вам нужно вернуть вызывающим абонентам массив с динамической типизацией (зачем? Используйте вместо этого List), вам нужно использовать отражение. - person dimo414; 22.08.2016
comment
@newacct, хотя вы правы, что если вы не открываете общий массив, все будет в порядке, он будет работать только с незакрепленными типами. Например, это все равно не удастся static <T extends Foo> void testMe(T elem) { T[] array = (T[]) new Object[]{elem}; }, потому что в этом случае будет удален тип Foo, а не Object. - person Eugene; 10.11.2018
comment
@Eugene: вы должны использовать стирание T в качестве типа компонента при создании массива. Итак, если стирание T равно Foo, вы должны сделать array = (T[])new Foo[length]; - person newacct; 11.11.2018
comment
Вы не можете делать это всегда: softwareengineering.stackexchange.com/a/331109/4475 - person Brad Mace; 29.11.2018

Вот как использовать универсальные шаблоны, чтобы получить массив именно того типа, который вы ищете, сохраняя при этом безопасность типа (в отличие от других ответов, которые либо вернут вам массив 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 обозначает тип, который представляет объект 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));
}
person gdejohn    schedule 19.11.2010
comment
Это бесполезно, это просто сложный способ написать новую строку [...]. Но что действительно необходимо, так это что-то вроде public static ‹T› T [] newArray (int size) {...}, и этого просто не существует в java noir, его можно смоделировать с помощью отражения - причина в том, что информация о том, как создается экземпляр универсального типа, недоступный во время выполнения. - person Ingo; 21.03.2011
comment
@Ingo О чем ты говоришь? Мой код можно использовать для создания массива любого типа. - person gdejohn; 23.03.2011
comment
@Charlatan: Конечно, но и new [] тоже. Вопрос в том, кто знает тип и когда. Следовательно, если все, что у вас есть, - это универсальный тип, вы не можете этого сделать. - person Ingo; 23.03.2011
comment
@Ingo Это статично. Это динамично. Я не уверен, что вы не понимаете. - person gdejohn; 23.03.2011
comment
Я в этом не сомневаюсь. Дело в том, что вы не получаете объект Class во время выполнения для универсального типа X. - person Ingo; 23.03.2011
comment
@Ingo См. Изменение моего ответа. Ты не об этом говоришь? Потому что это компилируется без предупреждения и возвращает массив желаемого типа. - person gdejohn; 23.03.2011
comment
Почти. Я признаю, что это больше, чем может быть достигнуто с помощью new []. На практике это почти всегда работает. Однако по-прежнему невозможно, например, написать класс контейнера, параметризованный с помощью E, который имеет метод E [] toArray () и действительно возвращает истинный массив E []. Ваш код может быть применен только тогда, когда в коллекции есть хотя бы один E-объект. Итак, общее решение невозможно. - person Ingo; 23.03.2011
comment
@Ingo Если вам нужна эта функциональность и вы действительно хотите избежать необходимости подавлять какие-либо предупреждения, тогда у вас может быть поле экземпляра типа Class, а конструкторы принимают соответствующий аргумент для его инициализации. Это можно сделать безопасным для типов способом. - person gdejohn; 23.03.2011
comment
@Charlatan Как получить объект класса для типа ArrayList ‹String› безопасным для типов способом? getClass () вернет только ArrayList - без параметра. Получить ArrayList ‹String› [] тоже сложно. По-видимому, один «Невозможно создать общий массив ArrayList ‹String›», но массив ArrayList в порядке. - person misiu_mp; 11.08.2011
comment
@Charlatan А, значит, «иметь поле экземпляра типа Class и позволить конструкторам принимать соответствующий аргумент для его инициализации», в конце концов, невозможно сделать безопасным для типов способом? - person misiu_mp; 04.10.2011
comment
@misiu_mp Да, может. Это будет компилироваться без предупреждений и даст вам то, что вы хотите. Очевидно, это просто не вариант для ранее существовавшей реализации ArrayList. - person gdejohn; 05.10.2011
comment
@Charlatan Попробуйте использовать ArrayList ‹String› в качестве универсального аргумента в вашем примере. GenSet ‹ArrayList ‹String›› foo = new GenSet ‹ArrayList ‹String›› (ArrayList ‹String› [] .class, 1); Я получаю много ошибок в изобретении ArrayList ‹String› [] .class. - person misiu_mp; 11.10.2011
comment
@misiu_mp Вы не можете параметризовать литералы классов из-за стирания типа. - person gdejohn; 13.10.2011

Это единственный ответ, безопасный по типу

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}
person irreputable    schedule 08.11.2011
comment
Мне пришлось его найти, но да, второй аргумент длины для Arrays#copyOf() не зависит от длины массива, предоставленного в качестве первого аргумента. Это разумно, хотя и оплачивает вызовы Math#min() и System#arrayCopy(), ни один из которых не является строго необходимым для выполнения этой работы. - person seh; 04.10.2012
comment
@Radiodef - решение является типобезопасным во время компиляции. обратите внимание, что стирание не является частью спецификации языка; спецификация написана тщательно, чтобы в будущем мы могли получить полную реификацию - и тогда это решение будет отлично работать и во время выполнения, в отличие от других решений. - person ZhongYu; 18.05.2015
comment
@Radiodef - Спорный вопрос, является ли запрет создания универсального массива хорошей идеей. в любом случае язык оставляет лазейку - vararg требует создания универсального массива. Это так хорошо, как если бы язык разрешил new E[]. Проблема, которую вы показали в своем примере, является общей проблемой стирания, не уникальной для этого вопроса и этого ответа. - person ZhongYu; 18.05.2015
comment
@Radiodef - безопасность типов - это концепция времени компиляции. хорошо известно, что типобезопасная программа на Java может дать сбой во время выполнения, даже до появления дженериков, например апкастинг массива. у вас может быть другое понятие «типобезопасность», так что это просто разница в определении. но OP не ошибается, называя его типобезопасным. - person ZhongYu; 18.05.2015
comment
@Radiodef - это скорее академическая награда. (E[])new Object[n] небезопасен по типу; на самом деле это очевидно неправильно с языковой точки зрения. Так получилось сегодня работать, из-за стирания. Он потерпит неудачу при полном овеществлении. Но кого это волнует? Ну, те немногие, кто старается быть максимально безопасным с точки зрения типов, просто ради того, чтобы быть пуристом. - person ZhongYu; 18.05.2015
comment
@Radiodef - Есть некоторые отличия. Правильность этого решения проверяется компилятором; он не полагается на человеческое мышление принудительного применения. Для этой конкретной проблемы разница несущественна. Некоторым людям просто нравится немного фантазировать, вот и все. Если кого-то вводит в заблуждение формулировка ОП, это поясняется вашими и моими комментариями. - person ZhongYu; 18.05.2015
comment
Что, если E сам является параметризованным типом, например List ‹String›? - person uraj; 17.08.2015
comment
@irreputable Мне это нравится, но я не думаю, что вам нужен length, вы можете просто написать его как return Arrays.copyOf(Objects.requireNonNull(array), array.length); - person Eugene; 10.11.2018

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

См. _ 9_ для получения подробной информации.

person Jason C    schedule 15.08.2013
comment
+1 Были вопросы о создании многомерных массивов, которые закрываются как дубликаты этого поста, но ни один из ответов не касался этого конкретно. - person Paul Bellora; 15.08.2013
comment
@JordanC Может быть; хотя по духу он такой же, как stackoverflow.com/a/5671304/616460; Я подумаю о том, как лучше справиться завтра. Я сонный. - person Jason C; 12.11.2014

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

Например, это используется _ 3_.

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

person Radiodef    schedule 05.03.2014
comment
Для этого вам действительно не нужен специальный интерфейс, такой как ArraySupplier, вы можете объявить конструктор как GenSet(Supplier<E[]> supplier) { ... и вызвать его с той же строкой, что и у вас. - person Lii; 28.12.2015
comment
@Lii Чтобы быть таким же, как мой пример, это будет IntFunction<E[]>, но да, это правда. - person Radiodef; 28.12.2015

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

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

@SuppressWarnings({"unchecked"})

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

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

person Jeff Olson    schedule 09.02.2009

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

public class GenSet<T> {

    private final T[] array;

    @SafeVarargs
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        this.array = Arrays.copyOf(dummy, 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]
person saka1029    schedule 11.07.2017

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

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

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

person Bill Michell    schedule 11.02.2009

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

Обобщения используются для проверки типов во время компиляции. Следовательно, цель - проверить

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

Проверь это:

введите описание изображения здесь

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

person puneeth    schedule 14.06.2011

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

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

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

person Benjamin M    schedule 21.02.2016
comment
Неплохо, но работает только в том случае, если вы вызываете это «вручную», то есть передаете элементы по отдельности. Если вы не можете создать новый экземпляр T[], вы не можете программно создать T[] elems для передачи в функцию. И если бы вы могли, вам бы эта функция не понадобилась. - person orlade; 29.08.2016

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

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

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

person MatheusJardimB    schedule 08.08.2013
comment
Да, вы возвращаете null, что не является ожидаемым пустым массивом. Это лучшее, что вы можете сделать, но не идеально. - person Kevin Cox; 07.02.2014
comment
Это также может не сработать, если List содержит более одного типа объектов, например. toArray(Arrays.asList("abc", new Object())) выкинет ArrayStoreException. - person Radiodef; 06.04.2015
comment
Я использовал урезанную версию этого; Первое, что я смог использовать, это сработало, хотя, по общему признанию, я не пробовал некоторые из более сложных решений. Чтобы избежать цикла for и других, я использовал Arrays.fill(res, obj);, так как мне нужно было одинаковое значение для каждого индекса. - person bbarker; 10.08.2015

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

person KeyC0de    schedule 09.11.2016

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

public <T> T[] array(T... values) {
    return values;
}
person Rodrigo Asensio    schedule 15.09.2017

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

person Ola Bini    schedule 09.02.2009
comment
Второй пример (с использованием Array.newInstance ()) является фактически типизированным. Это возможно, потому что тип T объекта Class должен соответствовать T массива. По сути, он вынуждает вас предоставить информацию, которую среда выполнения Java отбрасывает для обобщений. - person Joachim Sauer; 10.02.2009

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

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.

person Bobster    schedule 31.08.2012

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

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

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

person Pedram Esmaeeli    schedule 12.06.2015

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

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

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

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

private K value;

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

person vnportnoy    schedule 14.09.2013

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

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(), является правильным типом (хотя мы это знаем).

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

person plugwash    schedule 17.10.2015

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

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

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

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

person developer747    schedule 19.10.2016

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

person Esko    schedule 09.02.2009
comment
Мы ищем длинные ответы, которые дают некоторое объяснение и контекст. Не отвечайте просто однострочно; объясните, почему ваш ответ правильный, в идеале с цитатами. Ответы без пояснений могут быть удалены. - person gparyani; 16.09.2014
comment
Но это не сработает в некоторых случаях, например, если ваш общий класс хочет реализовать интерфейс Comparable. - person RamPrasadBismil; 21.04.2016
comment
Полагаю, добро пожаловать сюда семь лет назад. - person Esko; 26.04.2016
comment
Это не сработает, если вы попытаетесь вернуть массив из универсального кода неуниверсальному вызывающему объекту. Будет классное исключение. - person plugwash; 12.07.2017

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

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>();
        }
    }
}
person David Bernard    schedule 13.02.2011
comment
Я не могу запустить ваш код, откуда взялся ваш Element класс? - person ; 21.02.2018

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

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
person StarMonkey    schedule 05.04.2012
comment
На самом деле это не работает. new Holder<Thing>[10] - это создание универсального массива. - person Radiodef; 10.03.2014

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

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

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

 Tuple<Long, String>[] tupleArray = new Tuple[10];
person Mohsen Afshin    schedule 21.08.2013
comment
Да, это не совсем связано, но коренится в тех же проблемах (стирание, ковариация массива). Вот пример сообщения о создании массивов параметризованных типов: stackoverflow.com / questions / 9542076 / - person Paul Bellora; 21.08.2013

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

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

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

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

person Cambot    schedule 09.07.2014
comment
Нет, не работает. Varargs создает стирание T, когда T является переменной типа, т.е. zeroArray возвращает Object[]. См. http://ideone.com/T8xF91. - person Radiodef; 06.04.2015

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

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

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}
person samir benzenine    schedule 15.09.2014
comment
Если вы собираетесь предложить это, вам действительно нужно объяснить его ограничения. Никогда не выставляйте a вне класса! - person Radiodef; 06.04.2015

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

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

а затем в вашем классе массива просто запустите его так:

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

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

Чтобы получить из массива, вы должны вызвать array [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++;
}
person Crab Nebula    schedule 28.06.2017
comment
Вопрос заключался в создании массива типа параметра универсального типа T, а не массива какого-либо параметризованного типа. - person Sotirios Delimanolis; 29.06.2017
comment
Тем не менее, он выполняет ту же задачу и не требует от вас включения класса, что упрощает использование вашей пользовательской коллекции. - person Crab Nebula; 30.06.2017
comment
Какая задача? Это буквально другая задача: массив параметризованного типа против массива параметра универсального типа. - person Sotirios Delimanolis; 30.06.2017
comment
Он позволяет создавать массив из универсального типа? Первоначальная проблема заключалась в инициализации массива с использованием универсального типа, который с использованием моего метода позволяет вам обойтись без того, чтобы пользователь нажимал на класс или выдавал неконтролируемую ошибку, такую ​​как попытка преобразовать объект в строку. Как и холод, я не лучший в том, чем занимаюсь, и я не ходил в школу по программированию, но я думаю, что все еще заслуживаю небольшого вклада, а не того, чтобы меня ругали другие дети в Интернете. - person Crab Nebula; 01.07.2017
comment
Я согласен с Сотиросом. Есть два способа придумать ответ. Либо это ответ на другой вопрос, либо попытка обобщить вопрос. Оба неверны / бесполезны. Люди, которые ищут руководство по реализации универсального класса массива, перестанут читать, когда прочитают заголовок вопроса. И когда они находят Q с 30 ответами, они вряд ли прокрутятся до конца и прочитают нулевой ответ от новичка SO. - person Stephen C; 12.07.2017
comment
Наконец, если они нашли вашу пятёрку органически и она была актуальной, то им повезло. Вы заполнили его не в том месте. - person Stephen C; 12.07.2017

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

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

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

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

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

person Sam Ginrich    schedule 22.05.2020

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

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
person Irfan Ul Haq    schedule 19.04.2018

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

import java.lang.reflect.Array;

class Stack<T> {
    private T[] array = null;
    private final int capacity = 10; // fixed or pass it in the constructor
    private int pos = 0;

    public void push(T value) {
        if (value == null)
            throw new IllegalArgumentException("Stack does not accept nulls");
        if (array == null)
            array = (T[]) Array.newInstance(value.getClass(), capacity);
        // put logic: e.g.
        if(pos == capacity)
             throw new IllegalStateException("push on full stack");
        array[pos++] = value;
    }

    public T pop() throws IllegalStateException {
        if (pos == 0)
            throw new IllegalStateException("pop on empty stack");
        return array[--pos];
    }
}

в этом случае вы используете java.lang.reflect.Array.newInstance для создания массива, и это будет не Object [], а настоящий T []. Вы не должны беспокоиться о том, что он не будет окончательным, поскольку он управляется внутри вашего класса. Обратите внимание, что вам нужен ненулевой объект в push (), чтобы иметь возможность использовать тип, поэтому я добавил проверку данных, которые вы отправляете, и выбросил там исключение.

Тем не менее, это несколько бессмысленно: вы сохраняете данные с помощью push, и это сигнатура метода, которая гарантирует, что будут введены только T-элементы. Так что более или менее неважно, является ли массив Object [] или T [].

person user1708042    schedule 06.11.2019

person    schedule
comment
Вы всегда должны добавлять объяснение к своему коду и объяснять, почему он решает исходный опубликованный вопрос. - person mjuarez; 03.06.2015