Можно ли создать экземпляр универсального типа в Java? На основании того, что я видел, я думаю, что ответ - no (из-за стирания типа), но мне было бы интересно, сможет ли кто-нибудь увидеть что-то, чего мне не хватает:
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
Обновлено: Оказывается, Жетоны супертипа можно использовать для решения моей проблемы, но для этого требуется много кода, основанного на отражении, как указано в некоторых из приведенных ниже ответов.
Я оставлю это открытым на некоторое время, чтобы посмотреть, не придумает ли кто-нибудь что-нибудь кардинально отличное от Статья Artima Яна Робертсона.
См. Также: stackoverflow.com/a/5684761/59087
В Java 8 теперь вы можете передавать ссылку на конструктор или лямбду, что делает эту проблему довольно тривиальной для решения. Подробнее см. мой ответ ниже.
Я считаю, что писать такой код - плохая идея, это более элегантные и удобочитаемые способы решения скрытой проблемы.
@DavidCitron "в течение некоторого времени" он сказал ... С тех пор прошло одиннадцать лет ...




Как вы сказали, вы не можете этого сделать из-за стирания типа. Вы можете сделать это с помощью отражения, но для этого потребуется много кода и много обработки ошибок.
Как бы вы вообще это сделали, используя отражение? Единственный метод, который я вижу, - это Class.getTypeParameters (), но он возвращает только объявленные типы, а не типы времени выполнения.
Вы об этом говорите? artima.com/weblogs/viewpost.jsp?thread=208860
Ты прав. Вы не можете сделать new E(). Но вы можете изменить его на
private static class SomeContainer<E> {
E createContents(Class<E> clazz) {
return clazz.newInstance();
}
}
Это боль. Но это работает. Заворачивание в заводской узор делает его немного более терпимым.
Да, я видел это решение, но оно работает, только если у вас уже есть ссылка на объект Class того типа, который вы хотите создать.
Да, я знаю. Было бы неплохо, если бы вы могли использовать E.class, но это просто дает вам Object.class из-за стирания :)
Это правильный подход к этой проблеме. Обычно это не то, что вы хотите, а то, что вы получаете.
Да, лучшее решение, если не считать перехода на внедрение зависимостей или создание фабрик по написанию.
А как вызвать метод createContents ()?
@Traroth, я думаю, вам нужно просто создать новый экземпляр класса и вызвать метод с существующим объектом того типа, который вам нужен. Например: new SomeContainer<String>("Test");.
@Alexis Dufrenoy - немного поздно, но для полноты ... sc = new SomeContainer<String>(); sc.createContents(String.class);. Насколько я понимаю, это единственный ответ, который сработает для OP. Я играл с другими, но получил ClassCastException, конвертирующий из Type в Class, или могу видеть <T> только внутри самого параметризованного класса. Интересно, однако, есть ли у других бетонные покрытия. +1 к этому и буду сдерживать мои голоса против других ... Я что-то упускаю?
Class<?> с использованием Guava и TypeToken, см. этот ответ для кода и ссылок!я собирался сделать это в заводском классе. мой суперкласс имеет универсальный тип, переданный ему при установке MyClass <P extends Basepresenter>. подклассу передается конкретный тип в скобках <>. затем я могу передать общий тип в свой Factory и теоретически создать любой тип докладчика. собираюсь поиграть с этим сегодня вечером
@ wmorrison365: И как это решение будет работать, если тип, который я хочу создать, требует параметров в своем конструкторе? Этот подход, похоже, работает только с типами конструкторов без аргументов ...
Привет @ pulp-fiction, вам нужно будет пройти через конструктор самого экземпляра Class - используя Class#getDeclaredConstructor и вызвать его, используя Constructor#newInstance. Например: (1) получить конструктор из класса: Constructor<? extends E> constructor = clazz.getDeclaredConstructor(new Class[] { String.class, String.class, boolean.class, long.class}); (2) затем вызвать параметризованный конструктор - необходимо получить значения instance = constructor.newInstance(aString, anotherString, aBool, aLong);
Спасибо, @JustinRudd! Почему стирание типа не применяется к Class<E> clazz, поэтому он становится Class<Object> clazz, а затем clazz.newInstance() возвращает экземпляр Object?
@Tim стирание шрифтов работает не так. Он не превращает Class<E> в Class<Object>, который по-прежнему был бы универсальным типом. Стирание типов превращает Class<E> в Class, но это все тот же объект Class во время выполнения, представляющий тот же самый фактический класс. Так же, как это работало до Generics, например. String.class.newInstance() всегда возвращает экземпляр String, но до Generics компилятор не узнает его и скажет вам, что вы должны преобразовать возвращенную ссылку типа Object в String. Но приведение ссылочного типа не меняет фактический тип объекта.
это решение работало, однако на самом деле оно помечено как устаревшее. и его следует заменить на: clazz.getDeclaredConstructor (). newInstance ()
Вот вариант, который я придумал, он может помочь:
public static class Container<E> {
private Class<E> clazz;
public Container(Class<E> clazz) {
this.clazz = clazz;
}
public E createContents() throws Exception {
return clazz.newInstance();
}
}
Обновлено: В качестве альтернативы вы можете использовать этот конструктор (но для этого требуется экземпляр E):
@SuppressWarnings("unchecked")
public Container(E instance) {
this.clazz = (Class<E>) instance.getClass();
}
Да, это работает одинаково даже без дженериков - с дженериками создание экземпляра этого контейнера становится немного избыточным (вы должны указать, что означает «E» дважды).
ну, вот что происходит, когда вы используете Java и дженерики ... они не очень хороши, и есть серьезные ограничения ...
Я не знаю, помогает ли это, но когда вы подклассифицируете (в том числе анонимно) общий тип, информация о типе становится доступной через отражение. например.,
public abstract class Foo<E> {
public E instance;
public Foo() throws Exception {
instance = ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
...
}
}
Итак, когда вы создаете подкласс Foo, вы получаете экземпляр Bar, например,
// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );
Но это большая работа, и она работает только для подклассов. Хотя может быть удобно.
Да, это хорошо, особенно если общий класс абстрактный, вы можете сделать это в конкретных подклассах :)
Этот метод также работает, если класс Foo не является абстрактным. Но почему он работает только с анонимными подклассами Foo? Предположим, мы делаем бетон Foo (мы не учитываем abstract), почему new Foo<Bar>(); приведет к ошибке, а new Foo<Bar>(){}; - нет? (Исключение: «Класс не может быть приведен к ParameterizedType»)
@TimKuipers <E> в class Foo<E> не привязан к какому-либо конкретному типу. Вы увидите исключительное поведение всякий раз, когда E не привязан к статически, например: new Foo<Bar>(), new Foo<T>() {...} или class Fizz <E> extends Foo<E>. Первый случай не связан статически, это стертый во время компиляции. Во втором случае вместо E заменяется другая переменная типа (T), но она все еще не привязана. И в последнем случае должно быть очевидно, что E еще не привязан.
Примером статической привязки параметра типа может быть class Fizz extends Foo<Bar> - в этом случае пользователи Fizz получают что-то, что является Foo<Bar> и не может быть чем-либо, кроме Foo<Bar>. В этом случае компилятор с радостью закодирует эту информацию в метаданные класса для Fizz и сделает ее доступной как ParameterizedType для кода отражения. Когда вы создаете анонимный внутренний класс, такой как new Foo<Bar>() {...}, он делает то же самое, за исключением того, что вместо Fizz компилятор генерирует «анонимное» имя класса, которое вы не узнаете, пока внешний класс не будет скомпилирован.
Следует отметить, что это не сработает, если аргументы типа также являются ParameterizedType. Например, Foo<Bar<Baz>>. Вы создадите экземпляр ParameterizedTypeImpl, который нельзя создать явно. Поэтому рекомендуется проверить, возвращает ли getActualTypeArguments()[0]ParameterizedType. Если это так, то вы хотите получить необработанный тип и вместо этого создать его экземпляр.
Почему тогда я не могу использовать getGenericSuperclass() прямо в коде?
@WilliamPrice, и важно подчеркнуть, что он не работает, если тип не фиксируется подклассом, например class Fizz<T> extends Foo<T> { … }, поэтому это хрупкая конструкция, поскольку базовый класс не может декларировать требования к форме подкласса.
@Holger Я думал, что в моем первом комментарии
@WilliamPrice правильно, я прочитал ваш второй комментарий, но в любом случае мы оба сказал это в комментарии, но этот пункт должен быть подчеркнул в ответе.
Вам понадобится какая-то абстрактная фабрика того или иного типа, чтобы переложить ответственность:
interface Factory<E> {
E create();
}
class SomeContainer<E> {
private final Factory<E> factory;
SomeContainer(Factory<E> factory) {
this.factory = factory;
}
E createContents() {
return factory.create();
}
}
..а как выглядит Factory.create ()?
@OhadR Factory<> - это интерфейс, поэтому тела нет. Дело в том, что вам нужен слой косвенных указаний, чтобы переложить ответственность на методы, которые «знают» код, необходимый для создания экземпляра. Гораздо лучше делать это с помощью обычного кода, а не металингвистических Class или Constructor, поскольку отражение приносит целый мир вреда.
Теперь вы можете создать экземпляр фабрики с таким выражением ссылки на метод: SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Если ты имеешь ввиду
new E()
тогда это невозможно. И я бы добавил, что это не всегда правильно - как узнать, есть ли у E общедоступный конструктор без аргументов?
Но вы всегда можете делегировать создание какому-либо другому классу, который знает, как создать экземпляр - это может быть Class<E> или ваш собственный код, подобный этому
interface Factory<E>{
E create();
}
class IntegerFactory implements Factory<Integer>{
private static int i = 0;
Integer create() {
return i++;
}
}
Если вы не хотите вводить имя класса дважды во время создания экземпляра, например:
new SomeContainer<SomeType>(SomeType.class);
Вы можете использовать заводской метод:
<E> SomeContainer<E> createContainer(Class<E> class);
Как в:
public class Container<E> {
public static <E> Container<E> create(Class<E> c) {
return new Container<E>(c);
}
Class<E> c;
public Container(Class<E> c) {
super();
this.c = c;
}
public E createInstance()
throws InstantiationException,
IllegalAccessException {
return c.newInstance();
}
}
Вы можете использовать:
Class.forName(String).getConstructor(arguments types).newInstance(arguments)
Но вам нужно указать точное имя класса, включая пакеты, например. java.io.FileInputStream. Я использовал это для создания парсера математических выражений.
И как получить точное имя класса универсального типа во время выполнения?
Вам нужно будет сохранить его, используя экземпляр этого класса. Выполнимо, но вряд ли удобно. Если у вашего универсального шаблона был член типа E (или T или что-то еще), получить его двоичное имя - это просто foo.getClass().getName(). Откуда появился ЭТОТ экземпляр? В настоящее время я передаю один в конструктор проекта, над которым сейчас работаю.
package org.foo.com;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* Basically the same answer as noah's.
*/
public class Home<E>
{
@SuppressWarnings ("unchecked")
public Class<E> getTypeParameterClass()
{
Type type = getClass().getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) type;
return (Class<E>) paramType.getActualTypeArguments()[0];
}
private static class StringHome extends Home<String>
{
}
private static class StringBuilderHome extends Home<StringBuilder>
{
}
private static class StringBufferHome extends Home<StringBuffer>
{
}
/**
* This prints "String", "StringBuilder" and "StringBuffer"
*/
public static void main(String[] args) throws InstantiationException, IllegalAccessException
{
Object object0 = new StringHome().getTypeParameterClass().newInstance();
Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
System.out.println(object0.getClass().getSimpleName());
System.out.println(object1.getClass().getSimpleName());
System.out.println(object2.getClass().getSimpleName());
}
}
Хороший подход с помощью этого кода может вызвать ClassCastException, если вы используете в универсальном универсальный тип. Затем вы извлекаете аргумент actualType. Вы должны проверить, что это также ParamterizedType, и, если да, вернуть его RawType (или что-то лучше, чем это). Другая проблема заключается в том, что мы расширяем более одного раза, когда этот код также генерирует ClassCastExeption.
Вызвано: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl не может быть преобразован в java.lang.Class
@ DamianLeszczyński-Vash также потерпит неудачу, например, class GenericHome<T> extends Home<T>{}
return (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
Это не работает в моем примере в исходном вопросе. Суперкласс для SomeContainer - это просто Object. Следовательно, this.getClass().getGenericSuperclass() возвращает Class (класс java.lang.Object), а не ParameterizedType. На это уже указывалось в ответе партнера stackoverflow.com/questions/75175/….
Совершенно неправильно: исключение в потоке «main» java.lang.ClassCastException: java.lang.Class не может быть преобразован в java.lang.reflect.ParameterizedType
Вы можете добиться этого с помощью следующего фрагмента:
import java.lang.reflect.ParameterizedType;
public class SomeContainer<E> {
E createContents() throws InstantiationException, IllegalAccessException {
ParameterizedType genericSuperclass = (ParameterizedType)
getClass().getGenericSuperclass();
@SuppressWarnings("unchecked")
Class<E> clazz = (Class<E>)
genericSuperclass.getActualTypeArguments()[0];
return clazz.newInstance();
}
public static void main( String[] args ) throws Throwable {
SomeContainer< Long > scl = new SomeContainer<>();
Long l = scl.createContents();
System.out.println( l );
}
}
Совершенно неправильно: исключение в потоке «main» java.lang.ClassCastException: java.lang.Class не может быть преобразован в java.lang.reflect.ParameterizedType
От Учебное пособие по Java - Ограничения на универсальные шаблоны:
Невозможно создать экземпляры параметров типа
Вы не можете создать экземпляр параметра типа. Например, следующий код вызывает ошибку времени компиляции:
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
В качестве обходного пути вы можете создать объект параметра типа посредством отражения:
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
Вы можете вызвать метод добавления следующим образом:
List<String> ls = new ArrayList<>();
append(ls, String.class);
Я думал, что смогу это сделать, но был разочарован: это не работает, но я думаю, что им все же стоит поделиться.
Может кто поправит:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface SomeContainer<E> {
E createContents();
}
public class Main {
@SuppressWarnings("unchecked")
public static <E> SomeContainer<E> createSomeContainer() {
return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
new Class[]{ SomeContainer.class }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> returnType = method.getReturnType();
return returnType.newInstance();
}
});
}
public static void main(String[] args) {
SomeContainer<String> container = createSomeContainer();
[*] System.out.println("String created: [" +container.createContents()+"]");
}
}
Производит:
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
at Main.main(Main.java:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Строка 26 - это строка с [*].
Единственное жизнеспособное решение - это решение @JustinRudd
Если вам нужен новый экземпляр аргумента типа внутри универсального класса, заставьте ваши конструкторы требовать его класс ...
public final class Foo<T> {
private Class<T> typeArgumentClass;
public Foo(Class<T> typeArgumentClass) {
this.typeArgumentClass = typeArgumentClass;
}
public void doSomethingThatRequiresNewT() throws Exception {
T myNewT = typeArgumentClass.newInstance();
...
}
}
Использование:
Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);
Плюсы:
Минусы:
Foo<L>. Для начала ... newInstance() вызовет воблер, если класс аргументов типа не имеет конструктора по умолчанию. В любом случае это относится ко всем известным решениям.Вы можете использовать загрузчик классов и имя класса, а также некоторые параметры.
final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);
это хуже, чем просто передать объект класса
Вам вообще не нужно явно ссылаться на загрузчик классов.
Когда вы работаете с E во время компиляции, вам все равно фактический общий тип «E» (вы либо используете отражение, либо работаете с базовым классом универсального типа), поэтому позвольте подклассу предоставить экземпляр E.
abstract class SomeContainer<E>
{
abstract protected E createContents();
public void doWork(){
E obj = createContents();
// Do the work with E
}
}
class BlackContainer extends SomeContainer<Black>{
protected Black createContents() {
return new Black();
}
}
Существуют различные библиотеки, которые могут разрешить E за вас, используя методы, подобные тем, которые обсуждались в статье Робертсона. Вот реализация createContents, которая использует ТипИнструменты для разрешения необработанного класса, представленного E:
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
Это предполагает, что getClass () преобразуется в подкласс SomeContainer и в противном случае завершится ошибкой, поскольку фактическое параметризованное значение E будет стерто во время выполнения, если оно не было захвачено в подклассе.
Вы можете сделать это сейчас, и это не требует кучи кода отражения.
import com.google.common.reflect.TypeToken;
public class Q26289147
{
public static void main(final String[] args) throws IllegalAccessException, InstantiationException
{
final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
final String string = (String) smpc.type.getRawType().newInstance();
System.out.format("string = \"%s\"",string);
}
static abstract class StrawManParameterizedClass<T>
{
final TypeToken<T> type = new TypeToken<T>(getClass()) {};
}
}
Конечно, если вам нужно вызвать конструктор, который потребует некоторого отражения, но это очень хорошо задокументировано, этого трюка нет!
Это решение работает для ограниченного набора случаев, как и ответ @noah с отражением. Я попробовал их все сегодня ... И закончил тем, что передал экземпляр класса параметров параметризованному классу (чтобы иметь возможность вызывать .newInstance ()). Очень большой недостаток "дженериков" ... new Foo <Bar> (Bar.class); ... class Foo <T> {закрытый final Class <T> mTFactory; Foo (Класс <T> tClass) {mTFactory = tClass; ...} T instance = tFactory.newInstance (); }
это работает во всех случаях, даже статические фабричные методы, которые принимают общие параметры
Улучшение ответа @Noah.
Причина изменения
а] Безопаснее, если используется более 1 универсального типа, если вы изменили порядок.
б] Сигнатура универсального типа класса меняется время от времени, так что вы не будете удивлены необъяснимыми исключениями во время выполнения.
Надежный код
public abstract class Clazz<P extends Params, M extends Model> {
protected M model;
protected void createModel() {
Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
for (Type type : typeArguments) {
if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
try {
model = ((Class<M>) type).newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
Или используйте один лайнер
Однострочный код
model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
Вот реализация createContents, которая использует ТипИнструменты для разрешения необработанного класса, представленного E:
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
Этот подход работает только в том случае, если SomeContainer является подклассом, поэтому фактическое значение E фиксируется в определении типа:
class SomeStringContainer extends SomeContainer<String>
В противном случае значение E стирается во время выполнения и не подлежит восстановлению.
Подумайте о более функциональном подходе: вместо создания E из ничего (что явно является запахом кода), передайте функцию, которая знает, как ее создать, т.е.
E createContents(Callable<E> makeone) {
return makeone.call(); // most simple case clearly not that useful
}
Технически вы не передаете функцию, вы передаете функциональный объект (также известный как функтор).
Или, может быть, чтобы преодолеть захват Exception, используйте вместо него Supplier<E>.
К сожалению, Java не позволяет делать то, что вы хотите. См. официальный обходной путь:
You cannot create an instance of a type parameter. For example, the following code causes a compile-time error:
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
As a workaround, you can create an object of a type parameter through reflection:
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
You can invoke the append method as follows:
List<String> ls = new ArrayList<>();
append(ls, String.class);
Не могли бы вы сказать мне, почему вы голосуете против, когда делаете это? Я не понимаю, почему официальный обходной путь - плохое решение. Спасибо.
Думаю, у вас меньше голосов, потому что ваш ответ по сути такой же, как и у Джастина Радда: stackoverflow.com/a/75254/103412
В Java 8 вы можете использовать функциональный интерфейс Supplier для этого довольно легко:
class SomeContainer<E> {
private Supplier<E> supplier;
SomeContainer(Supplier<E> supplier) {
this.supplier = supplier;
}
E createContents() {
return supplier.get();
}
}
Вы бы построили этот класс следующим образом:
SomeContainer<String> stringContainer = new SomeContainer<>(String::new);
Синтаксис String::new в этой строке - ссылка на конструктор.
Если ваш конструктор принимает аргументы, вы можете вместо этого использовать лямбда-выражение:
SomeContainer<BigInteger> bigIntegerContainer
= new SomeContainer<>(() -> new BigInteger(1));
Хороший. Это позволяет избежать размышлений и обработки исключений.
Так мило. К сожалению для пользователей Android, для этого требуется уровень API 24 или выше.
... и он не отличается от этот еще более старый ответ, показывая, что технический шаблон, лежащий в основе этого, даже старше, чем поддержка Java для лямбда-выражений и ссылок на методы, хотя вы даже можете использовать этот старый код с ними после обновления вашего компилятора ...
Можно ли было поставить только SomeContainer stringContainer = new SomeContainer(String::new);?
@AaronFranke: Нет, потому что тогда вы будете использовать необработанный тип.
Вот улучшенное решение, основанное на ParameterizedType.getActualTypeArguments, уже упомянутое @noah, @Lars Bohl и некоторыми другими.
Первое небольшое улучшение в реализации. Фабрика должна возвращать не экземпляр, а тип. Как только вы вернете экземпляр с использованием Class.newInstance(), вы уменьшите объем использования. Потому что так можно вызывать только конструкторы без аргументов. Лучший способ - вернуть тип и позволить клиенту выбрать, какой конструктор он хочет вызвать:
public class TypeReference<T> {
public Class<T> type(){
try {
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
throw new IllegalStateException("Could not define type");
}
if (pt.getActualTypeArguments().length != 1){
throw new IllegalStateException("More than one type has been found");
}
Type type = pt.getActualTypeArguments()[0];
String typeAsString = type.getTypeName();
return (Class<T>) Class.forName(typeAsString);
} catch (Exception e){
throw new IllegalStateException("Could not identify type", e);
}
}
}
Вот примеры использования. @Lars Bohl показал только знаменательный способ получить универсальное воплощение через расширение. @noah только через создание экземпляра с {}. Вот тесты, демонстрирующие оба случая:
import java.lang.reflect.Constructor;
public class TypeReferenceTest {
private static final String NAME = "Peter";
private static class Person{
final String name;
Person(String name) {
this.name = name;
}
}
@Test
public void erased() {
TypeReference<Person> p = new TypeReference<>();
Assert.assertNotNull(p);
try {
p.type();
Assert.fail();
} catch (Exception e){
Assert.assertEquals("Could not identify type", e.getMessage());
}
}
@Test
public void reified() throws Exception {
TypeReference<Person> p = new TypeReference<Person>(){};
Assert.assertNotNull(p);
Assert.assertEquals(Person.class.getName(), p.type().getName());
Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
Assert.assertNotNull(ctor);
Person person = (Person) ctor.newInstance(NAME);
Assert.assertEquals(NAME, person.name);
}
static class TypeReferencePerson extends TypeReference<Person>{}
@Test
public void reifiedExtenension() throws Exception {
TypeReference<Person> p = new TypeReferencePerson();
Assert.assertNotNull(p);
Assert.assertEquals(Person.class.getName(), p.type().getName());
Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
Assert.assertNotNull(ctor);
Person person = (Person) ctor.newInstance(NAME);
Assert.assertEquals(NAME, person.name);
}
}
Примечание: вы можете заставить клиентов TypeReference всегда использовать {} при создании экземпляра, сделав этот класс абстрактным: public abstract class TypeReference<T>. Я этого не делал, только чтобы показать стертый тестовый пример.
Надеюсь, еще не поздно помочь !!!
Java является типобезопасной, что означает, что только объекты могут создавать экземпляры.
В моем случае я не могу передать параметры методу createContents. В моем решении используется расширение, в отличие от ответа ниже.
private static class SomeContainer<E extends Object> {
E e;
E createContents() throws Exception{
return (E) e.getClass().getDeclaredConstructor().newInstance();
}
}
Это мой примерный случай, когда я не могу передавать параметры.
public class SomeContainer<E extends Object> {
E object;
void resetObject throws Exception{
object = (E) object.getClass().getDeclaredConstructor().newInstance();
}
}
Использование отражения создает ошибку времени выполнения, если вы расширяете свой универсальный класс без объекта типа. Чтобы расширить ваш универсальный тип до объекта, преобразуйте эту ошибку в ошибку времени компиляции.
что ты можешь сделать, это -
Сначала объявите переменную этого универсального класса
2.Затем создайте из него конструктор и создайте экземпляр этого объекта.
Затем используйте его везде, где хотите.
пример-
1
private Class<E> entity;
2
public xyzservice(Class<E> entity) {
this.entity = entity;
}
public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
return entity.newInstance();
}
3.
E e = getEntity(entity);
return entity.newInstance (); вызывает предупреждение: «Метод newInstance () из типа Class <E> устарел с версии 9»
Только что протестировал производительность на Android устройстве. 10000 операций и: 8-9 мс занимает new SomeClass (), 9-11 мс занимает Factory <SomeClass> .createInstance () и 64-71 мс занимает самое короткое отражение: SomeClass z = SomeClass.class.newInstance (). И все тесты были в одном блоке try-catch. Отражение newInstance () выдает 4 разных исключения, помните? Поэтому я решил использовать заводской шаблон.