Из-за реализации обобщений 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;
}
Но я действительно не понимаю, что происходит.
java
arrays
generics
reflection
instantiation
tatsuhirosatou
источник
источник
Ответы:
Я должен задать вопрос в ответ: ваш
GenSet
"проверен" или "не проверен"? Что это обозначает?Проверено : сильная типизация .
GenSet
точно знает, какой тип объектов он содержит (т.е. его конструктор был явно вызван сClass<E>
аргументом, и методы будут генерировать исключение, когда им передаются аргументы, не являющиеся типомE
. СмCollections.checkedCollection
.-> в этом случае вы должны написать:
Не проверено : слабая типизация . Проверка типов на самом деле не выполняется ни для одного из объектов, переданных в качестве аргумента.
-> в этом случае, вы должны написать
Обратите внимание, что тип компонента массива должен быть стиранием параметра типа:
Все это происходит из-за известной и преднамеренной слабости обобщений в Java: он был реализован с использованием стирания, поэтому «универсальные» классы не знают, с каким аргументом типа они были созданы во время выполнения, и поэтому не могут предоставить тип-тип. безопасность, если не реализован какой-либо явный механизм (проверка типов).
источник
Object[] EMPTY_ELEMENTDATA = {}
для хранения. Могу ли я использовать этот механизм для изменения размера, не зная тип с использованием обобщений?public void <T> T[] newArray(Class<T> type, int length) { ... }
Ты можешь сделать это:
Это один из предложенных способов реализации универсальной коллекции в эффективной Java; Пункт 26 . Нет ошибок типа, нет необходимости повторно приводить массив. Однако это вызывает предупреждение, потому что это потенциально опасно, и его следует использовать с осторожностью. Как подробно описано в комментариях,
Object[]
теперь это маскируется под нашE[]
тип и может вызывать неожиданные ошибки илиClassCastException
s, если используется небезопасно.Как правило, такое поведение безопасно, если массив приведения используется внутренне (например, для поддержки структуры данных), а не возвращается или подвергается воздействию клиентского кода. Если вам нужно вернуть массив универсального типа в другой код, упомянутый
Array
вами класс отражения - правильный путь.Стоит отметить, что везде, где это возможно, вы будете гораздо счастливее работать с
List
s, а не с массивами, если используете дженерики. Конечно, иногда у вас нет выбора, но использование фреймворка коллекций гораздо надежнее.источник
String[] s=b;
в приведенном вышеtest()
методе. Это потому, что массив E не совсем, это Object []. Это важно, если вы хотите, напримерList<String>[]
- вы не можете использоватьObject[]
для этого, вы должны иметьList[]
специально. Вот почему вам нужно использовать отраженный класс <?> Для создания массива.public E[] toArray() { return (E[])internalArray.clone(); }
когдаinternalArray
напечатано какE[]
, и, следовательно, на самом деле являетсяObject[]
. Это завершается с ошибкой во время выполнения с исключением приведения типа, потому чтоObject[]
невозможно присвоить массиву любого типаE
.E[] b = (E[])new Object[1];
вы можете ясно видеть , что единственная ссылка на созданный массив являетсяb
и что типb
являетсяE[]
. Поэтому нет никакой опасности, что вы случайно получите доступ к одному и тому же массиву через другую переменную другого типа. Если вместо этого у вас есть,Object[] a = new Object[1]; E[]b = (E[])a;
то вы должны быть параноиком о том, как вы используетеa
.Вот как использовать обобщенные элементы для получения массива именно того типа, который вы ищете при сохранении безопасности типов (в отличие от других ответов, которые либо вернут вам
Object
массив, либо приведут к предупреждениям во время компиляции):Он компилируется без предупреждений, и, как вы можете видеть
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
injava.lang.reflect.Array
возвращает в видеObject
массива типа, представленногоClass
объектом, переданным в качестве первого аргумента, и длины, указанной вint
качестве второго аргумента. Вызов методаgetComponentType
возвращаетClass
объект, представляющий тип компонента массива, представленногоClass
объектом, для которого был вызван метод (например,String.class
дляString[].class
,null
еслиClass
объект не представляет массив).Это последнее предложение не совсем точно. Вызов
String[].class.getComponentType()
возвращаетClass
объект, представляющий классString
, но его тип -Class<?>
нетClass<String>
, поэтому вы не можете сделать что-то вроде следующего.То же самое касается каждого метода,
Class
который возвращаетClass
объект.Что касается комментария Йоахима Сауэра к этому ответу (у меня недостаточно репутации, чтобы самому комментировать его), то пример использования приведения к
T[]
приведёт к предупреждению, потому что компилятор не может гарантировать безопасность типов в этом случае.Изменить комментарии Инго:
источник
Это единственный ответ, который является безопасным типом
источник
Arrays#copyOf()
не зависит от длины массива, предоставленного в качестве первого аргумента. Это умно, хотя и оплачивает стоимость звонков,Math#min()
иSystem#arrayCopy()
ни один из них не является строго необходимым для выполнения этой работы. docs.oracle.com/javase/7/docs/api/java/util/...E
переменная типа. Varargs создает массив стиранияE
когдаE
является переменной типа, что мало чем отличается от него(E[])new Object[n]
. Пожалуйста, смотрите http://ideone.com/T8xF91 . Это ни в коем случае не более безопасный тип, чем любой другой ответ.new E[]
. Проблема, которую вы показали в своем примере, является общей проблемой стирания, не уникальной для этого вопроса и этого ответа.Чтобы расширить на другие измерения, просто добавьте
[]
's и параметры измерения кnewInstance()
(T
является параметром типа,cls
является aClass<T>
,d1
черезd5
являются целыми числами):Смотрите
Array.newInstance()
подробности.источник
В Java 8 мы можем сделать своего рода создание универсального массива, используя лямбда или ссылку на метод. Это похоже на рефлексивный подход (который проходит a
Class
), но здесь мы не используем рефлексию.Например, это используется
<A> A[] Stream.toArray(IntFunction<A[]>)
.Это также может быть сделано до Java 8 с использованием анонимных классов, но это более громоздко.
источник
ArraySupplier
этому, вы можете объявить конструктор какGenSet(Supplier<E[]> supplier) { ...
и вызвать его с той же строкой, что и у вас.IntFunction<E[]>
, но да, это правда.Это описано в главе 5 (Общие положения) Effective Java, 2-е издание , пункт 25 ... Предпочитать списки массивам
Ваш код будет работать, хотя он будет генерировать непроверенное предупреждение (которое вы можете отключить с помощью следующей аннотации:
Однако, вероятно, было бы лучше использовать List вместо Array.
На сайте проекта OpenJDK есть интересное обсуждение этой ошибки / функции .
источник
Вам не нужно передавать аргумент Class в конструктор. Попробуй это.
а также
результат:
источник
Обобщения Java работают, проверяя типы во время компиляции и вставляя соответствующие приведения, но стирая типы в скомпилированных файлах. Это делает универсальные библиотеки пригодными для использования кодом, который не понимает универсальные (что было преднамеренным дизайнерским решением), но это означает, что вы обычно не можете узнать, что это за тип во время выполнения.
Общественный
Stack(Class<T> clazz,int capacity)
конструктор требует , чтобы передать объект Class во время выполнения, в котором информация средства класса является доступны во время выполнения в коде , который нуждается в этом. ИClass<T>
форма означает, что компилятор проверит, что передаваемый вами объект Class является именно объектом Class для типа T. Не подклассом T, не суперклассом T, но именно T.Затем это означает, что вы можете создать в своем конструкторе объект массива соответствующего типа, а это означает, что тип объектов, хранящихся в вашей коллекции, будет проверяться по типам в момент их добавления в коллекцию.
источник
Привет, хотя поток мертв, я хотел бы обратить ваше внимание на это:
Generics используется для проверки типов во время компиляции:
Не беспокойтесь о предупреждениях при вводе типов при написании универсального класса. Беспокойство, когда вы используете его.
источник
Как насчет этого решения?
Это работает и выглядит слишком просто, чтобы быть правдой. Есть ли недостаток?
источник
T[]
, вы не можете программно создать aT[] elems
для передачи в функцию. И если бы ты мог, тебе бы не понадобилась эта функция.Посмотрите также на этот код:
Он преобразует список объектов любого типа в массив того же типа.
источник
List
объект имеет более одного типа, напримерtoArray(Arrays.asList("abc", new Object()))
, выбрасываетArrayStoreException
.for
цикла и других я использовал,Arrays.fill(res, obj);
так как я хотел одно и то же значение для каждого индекса.Я нашел быстрый и простой способ, который работает для меня. Обратите внимание, что я использовал это только на Java JDK 8. Я не знаю, будет ли он работать с предыдущими версиями.
Хотя мы не можем создать экземпляр универсального массива с параметром определенного типа, мы можем передать уже созданный массив в конструктор универсального класса.
Теперь в main мы можем создать массив следующим образом:
Для большей гибкости с вашими массивами вы можете использовать связанный список, например. ArrayList и другие методы, найденные в классе Java.util.ArrayList.
источник
В примере используется отражение Java для создания массива. Делать это обычно не рекомендуется, так как это небезопасно. Вместо этого вам нужно просто использовать внутренний List и вообще избегать массива.
источник
Передача списка значений ...
источник
Я сделал этот фрагмент кода, чтобы рефлексивно создать экземпляр класса, который передается для простой автоматизированной утилиты тестирования.
Обратите внимание на этот сегмент:
для инициации массива, где Array.newInstance (класс массива, размер массива) . Класс может быть как примитивным (int.class), так и объектом (Integer.class).
BeanUtils является частью Spring.
источник
На самом деле более простой способ сделать это - создать массив объектов и привести его к нужному типу, как показано в следующем примере:
где
SIZE
константа иT
идентификатор типаисточник
Принудительный актерский состав, предложенный другими людьми, не работал для меня, за исключением исключения незаконного актерского состава.
Однако это неявное приведение работало нормально:
где Item является классом I, который содержит член:
Таким образом, вы получаете массив типа K (если элемент имеет только значение) или любой универсальный тип, который вы хотите определить в классе Item.
источник
Никто другой не ответил на вопрос о том, что происходит в приведенном вами примере.
Как уже говорили другие, дженерики «стираются» во время компиляции. Таким образом, во время выполнения экземпляр универсального не знает, каков его тип компонента. Причиной этого является историческая причина: Sun хотела добавить универсальные шаблоны, не нарушая существующий интерфейс (как исходный, так и двоичный).
Массивы с другой стороны делать знают их компоненты типа во время выполнения.
Этот пример решает проблему, когда код, вызывающий конструктор (который знает тип), передает параметр, сообщающий классу требуемый тип.
Таким образом, приложение будет создавать класс с чем-то вроде
и конструктор теперь знает (во время выполнения), что представляет собой тип компонента, и может использовать эту информацию для создания массива через API отражения.
Наконец, у нас есть приведение типа, потому что компилятор не может знать, что возвращаемый массив
Array#newInstance()
является правильным типом (даже если мы знаем).Этот стиль немного уродлив, но иногда он может быть наименее плохим решением для создания универсальных типов, которым по какой-либо причине необходимо знать их тип компонента во время выполнения (создание массивов или создание экземпляров их типа компонентов и т. Д.).
источник
Я нашел способ обойти эту проблему.
В строке ниже выдается ошибка создания общего массива
Однако, если я инкапсулирую
List<Person>
в отдельный класс, это работает.Вы можете выставлять людей в классе PersonList через геттер. Строка ниже даст вам массив, который есть
List<Person>
в каждом элементе. Другими словами массивList<Person>
.Мне нужно было что-то подобное в некотором коде, над которым я работал, и это то, что я сделал, чтобы заставить его работать. Пока проблем нет.
источник
Вы можете создать массив объектов и привести его к E везде. Да, это не очень чистый способ сделать это, но он должен по крайней мере работать.
источник
попробуй это.
источник
Element
класс?Простой, хотя и грязный, обходной путь к этому заключался бы в том, чтобы поместить второй класс-держатель в ваш основной класс и использовать его для хранения ваших данных.
источник
new Holder<Thing>[10]
является созданием универсального массива.Может быть, не имеет отношения к этому вопросу, но пока я получаю "
generic array creation
" ошибку для использованияЯ нахожу следующие работы (и работал для меня) с
@SuppressWarnings({"unchecked"})
:источник
Мне интересно, если этот код создаст эффективный универсальный массив?
Изменить: Возможно, альтернативный способ создания такого массива, если требуемый размер был известен и мал, было бы просто ввести необходимое число «ноль» в команду zeroArray?
Хотя очевидно, что это не так универсально, как использование кода createArray.
источник
T
когдаT
является переменной типа, то естьzeroArray
возвращаетObject[]
. См. Http://ideone.com/T8xF91 .Вы можете использовать приведение:
источник
a
вне класса!На самом деле я нашел довольно уникальное решение, чтобы обойти невозможность инициировать универсальный массив. То, что вам нужно сделать, это создать класс, который принимает переменную общего типа T следующим образом:
а затем в вашем классе массива просто начните так:
запуск
new Generic Invoker[]
вызывает проблему с непроверенным, но на самом деле не должно быть никаких проблем.Чтобы получить из массива, вы должны вызвать массив [i] .variable следующим образом:
Остальное, например изменение размера массива, можно сделать с помощью Arrays.copyOf () следующим образом:
И функция добавления может быть добавлена так:
источник
T
, а не массива какого-либо параметризованного типа.Согласно внпортному синтаксису
создает массив нулевых ссылок, которые будут заполнены как
который типа безопасен.
источник
источник
Создание общего массива запрещено в Java, но вы можете сделать это как
источник