Создать экземпляр универсального типа в Java?

Можно ли создать экземпляр универсального типа в Java? На основании того, что я видел, я думаю, что ответ - no (из-за стирания типа), но мне было бы интересно, сможет ли кто-нибудь увидеть что-то, чего мне не хватает:

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

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

Я оставлю это открытым на некоторое время, чтобы посмотреть, не придумает ли кто-нибудь что-нибудь кардинально отличное от Статья Artima Яна Робертсона.

Только что протестировал производительность на Android устройстве. 10000 операций и: 8-9 мс занимает new SomeClass (), 9-11 мс занимает Factory <SomeClass> .createInstance () и 64-71 мс занимает самое короткое отражение: SomeClass z = SomeClass.class.newInstance (). И все тесты были в одном блоке try-catch. Отражение newInstance () выдает 4 разных исключения, помните? Поэтому я решил использовать заводской шаблон.

Deepscorn 15.05.2014 23:38

См. Также: stackoverflow.com/a/5684761/59087

Dave Jarvis 04.03.2015 05:22

В Java 8 теперь вы можете передавать ссылку на конструктор или лямбду, что делает эту проблему довольно тривиальной для решения. Подробнее см. мой ответ ниже.

Daniel Pryden 30.03.2016 19:54

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

Krzysztof Cichocki 21.03.2017 14:53

@DavidCitron "в течение некоторого времени" он сказал ... С тех пор прошло одиннадцать лет ...

MC Emperor 21.08.2019 21:55
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
600
5
490 265
27

Ответы 27

Как вы сказали, вы не можете этого сделать из-за стирания типа. Вы можете сделать это с помощью отражения, но для этого потребуется много кода и много обработки ошибок.

Как бы вы вообще это сделали, используя отражение? Единственный метод, который я вижу, - это Class.getTypeParameters (), но он возвращает только объявленные типы, а не типы времени выполнения.

David Citron 16.09.2008 22:13

Вы об этом говорите? artima.com/weblogs/viewpost.jsp?thread=208860

David Citron 16.09.2008 22:52

Ты прав. Вы не можете сделать new E(). Но вы можете изменить его на

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

Это боль. Но это работает. Заворачивание в заводской узор делает его немного более терпимым.

Да, я видел это решение, но оно работает, только если у вас уже есть ссылка на объект Class того типа, который вы хотите создать.

David Citron 16.09.2008 22:14

Да, я знаю. Было бы неплохо, если бы вы могли использовать E.class, но это просто дает вам Object.class из-за стирания :)

Justin Rudd 16.09.2008 22:43

Это правильный подход к этой проблеме. Обычно это не то, что вы хотите, а то, что вы получаете.

Joachim Sauer 18.12.2008 01:13

Да, лучшее решение, если не считать перехода на внедрение зависимостей или создание фабрик по написанию.

Joseph Lust 13.06.2012 22:24

А как вызвать метод createContents ()?

Alexis Dufrenoy 12.08.2012 01:36

@Traroth, я думаю, вам нужно просто создать новый экземпляр класса и вызвать метод с существующим объектом того типа, который вам нужен. Например: new SomeContainer<String>("Test");.

Sam 19.10.2012 14:12

@Alexis Dufrenoy - немного поздно, но для полноты ... sc = new SomeContainer<String>(); sc.createContents(String.class);. Насколько я понимаю, это единственный ответ, который сработает для OP. Я играл с другими, но получил ClassCastException, конвертирующий из Type в Class, или могу видеть <T> только внутри самого параметризованного класса. Интересно, однако, есть ли у других бетонные покрытия. +1 к этому и буду сдерживать мои голоса против других ... Я что-то упускаю?

wmorrison365 10.04.2014 15:14
Это больше не единственный способ сделать это, теперь есть лучший способ, который не требует передачи ссылки Class<?> с использованием Guava и TypeToken, см. этот ответ для кода и ссылок!
user177800 08.08.2014 06:09

я собирался сделать это в заводском классе. мой суперкласс имеет универсальный тип, переданный ему при установке MyClass <P extends Basepresenter>. подклассу передается конкретный тип в скобках <>. затем я могу передать общий тип в свой Factory и теоретически создать любой тип докладчика. собираюсь поиграть с этим сегодня вечером

filthy_wizard 10.06.2016 18:46

@ wmorrison365: И как это решение будет работать, если тип, который я хочу создать, требует параметров в своем конструкторе? Этот подход, похоже, работает только с типами конструкторов без аргументов ...

Manish Kumar Sharma 19.05.2017 12:08

Привет @ 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);

wmorrison365 22.05.2017 14:26

Спасибо, @JustinRudd! Почему стирание типа не применяется к Class<E> clazz, поэтому он становится Class<Object> clazz, а затем clazz.newInstance() возвращает экземпляр Object?

Tim 29.01.2018 20:09

@Tim стирание шрифтов работает не так. Он не превращает Class<E> в Class<Object>, который по-прежнему был бы универсальным типом. Стирание типов превращает Class<E> в Class, но это все тот же объект Class во время выполнения, представляющий тот же самый фактический класс. Так же, как это работало до Generics, например. String.class.newInstance() всегда возвращает экземпляр String, но до Generics компилятор не узнает его и скажет вам, что вы должны преобразовать возвращенную ссылку типа Object в String. Но приведение ссылочного типа не меняет фактический тип объекта.

Holger 10.01.2019 11:56

это решение работало, однако на самом деле оно помечено как устаревшее. и его следует заменить на: clazz.getDeclaredConstructor (). newInstance ()

eriknyk 17.04.2020 19:58

Вот вариант, который я придумал, он может помочь:

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» дважды).

David Citron 16.09.2008 22:17

ну, вот что происходит, когда вы используете Java и дженерики ... они не очень хороши, и есть серьезные ограничения ...

Mike Stone 16.09.2008 22:20

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

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

Но это большая работа, и она работает только для подклассов. Хотя может быть удобно.

Да, это хорошо, особенно если общий класс абстрактный, вы можете сделать это в конкретных подклассах :)

Pierre Henry 18.04.2013 18:36

Этот метод также работает, если класс Foo не является абстрактным. Но почему он работает только с анонимными подклассами Foo? Предположим, мы делаем бетон Foo (мы не учитываем abstract), почему new Foo<Bar>(); приведет к ошибке, а new Foo<Bar>(){}; - нет? (Исключение: «Класс не может быть приведен к ParameterizedType»)

Tim Kuipers 22.12.2013 18:52

@TimKuipers <E> в class Foo<E> не привязан к какому-либо конкретному типу. Вы увидите исключительное поведение всякий раз, когда E не привязан к статически, например: new Foo<Bar>(), new Foo<T>() {...} или class Fizz <E> extends Foo<E>. Первый случай не связан статически, это стертый во время компиляции. Во втором случае вместо E заменяется другая переменная типа (T), но она все еще не привязана. И в последнем случае должно быть очевидно, что E еще не привязан.

William Price 25.04.2014 08:27

Примером статической привязки параметра типа может быть class Fizz extends Foo<Bar> - в этом случае пользователи Fizz получают что-то, что является Foo<Bar> и не может быть чем-либо, кроме Foo<Bar>. В этом случае компилятор с радостью закодирует эту информацию в метаданные класса для Fizz и сделает ее доступной как ParameterizedType для кода отражения. Когда вы создаете анонимный внутренний класс, такой как new Foo<Bar>() {...}, он делает то же самое, за исключением того, что вместо Fizz компилятор генерирует «анонимное» имя класса, которое вы не узнаете, пока внешний класс не будет скомпилирован.

William Price 25.04.2014 08:32

Следует отметить, что это не сработает, если аргументы типа также являются ParameterizedType. Например, Foo<Bar<Baz>>. Вы создадите экземпляр ParameterizedTypeImpl, который нельзя создать явно. Поэтому рекомендуется проверить, возвращает ли getActualTypeArguments()[0]ParameterizedType. Если это так, то вы хотите получить необработанный тип и вместо этого создать его экземпляр.

crush 20.08.2014 19:42

Почему тогда я не могу использовать getGenericSuperclass() прямо в коде?

Tomáš Zato - Reinstate Monica 01.05.2015 05:40

@WilliamPrice, и важно подчеркнуть, что он не работает, если тип не фиксируется подклассом, например class Fizz<T> extends Foo<T> { … }, поэтому это хрупкая конструкция, поскольку базовый класс не может декларировать требования к форме подкласса.

Holger 10.01.2019 12:01

@Holger Я думал, что в моем первом комментарии

William Price 10.01.2019 16:41

@WilliamPrice правильно, я прочитал ваш второй комментарий, но в любом случае мы оба сказал это в комментарии, но этот пункт должен быть подчеркнул в ответе.

Holger 10.01.2019 17:08

Вам понадобится какая-то абстрактная фабрика того или иного типа, чтобы переложить ответственность:

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 27.01.2016 15:43

@OhadR Factory<> - это интерфейс, поэтому тела нет. Дело в том, что вам нужен слой косвенных указаний, чтобы переложить ответственность на методы, которые «знают» код, необходимый для создания экземпляра. Гораздо лучше делать это с помощью обычного кода, а не металингвистических Class или Constructor, поскольку отражение приносит целый мир вреда.

Tom Hawtin - tackline 27.01.2016 20:29

Теперь вы можете создать экземпляр фабрики с таким выражением ссылки на метод: SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);

Lii 07.04.2018 14:31

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

И как получить точное имя класса универсального типа во время выполнения?

David Citron 23.12.2008 11:05

Вам нужно будет сохранить его, используя экземпляр этого класса. Выполнимо, но вряд ли удобно. Если у вашего универсального шаблона был член типа E (или T или что-то еще), получить его двоичное имя - это просто foo.getClass().getName(). Откуда появился ЭТОТ экземпляр? В настоящее время я передаю один в конструктор проекта, над которым сейчас работаю.

Mark Storer 16.08.2019 16:36

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.

Damian Leszczyński - Vash 10.09.2010 14:44

Вызвано: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl не может быть преобразован в java.lang.Class

juan 04.12.2012 18:45

@ DamianLeszczyński-Vash также потерпит неудачу, например, class GenericHome<T> extends Home<T>{}

Holger 10.01.2019 12:09

return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

Это не работает в моем примере в исходном вопросе. Суперкласс для SomeContainer - это просто Object. Следовательно, this.getClass().getGenericSuperclass() возвращает Class (класс java.lang.Object), а не ParameterizedType. На это уже указывалось в ответе партнера stackoverflow.com/questions/75175/….

David Citron 09.07.2011 21:54

Совершенно неправильно: исключение в потоке «main» java.lang.ClassCastException: java.lang.Class не может быть преобразован в java.lang.reflect.ParameterizedType

Aubin 03.01.2013 23:56

Вы можете добиться этого с помощью следующего фрагмента:

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

Aubin 03.01.2013 23:51

От Учебное пособие по 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);

Плюсы:

  • Намного проще (и менее проблематично), чем подход Робертсона с использованием токена супертипа (STT).
  • Намного более эффективен, чем подход STT (который съест ваш мобильный телефон на завтрак).

Минусы:

  • Невозможно передать класс конструктору по умолчанию (поэтому Foo является окончательным). Если вам действительно нужен конструктор по умолчанию, вы всегда можете добавить метод установки, но тогда вы должны не забыть вызвать ему позже.
  • Возражение Робертсона ... Больше баров, чем паршивая овца (хотя указание класса аргумента типа еще раз не убьет вас). И вопреки утверждениям Робертсона, это в любом случае не нарушает принцип DRY, потому что компилятор гарантирует правильность типа.
  • Не полностью защищен от Foo<L>. Для начала ... newInstance() вызовет воблер, если класс аргументов типа не имеет конструктора по умолчанию. В любом случае это относится ко всем известным решениям.
  • Не хватает полной инкапсуляции подхода STT. Однако это не имеет большого значения (учитывая возмутительные накладные расходы на производительность STT).

Вы можете использовать загрузчик классов и имя класса, а также некоторые параметры.

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

это хуже, чем просто передать объект класса

newacct 05.02.2014 01:26

Вам вообще не нужно явно ссылаться на загрузчик классов.

Stefan Reich 04.12.2017 18:00

Когда вы работаете с 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()) {};
    }
}

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

Вот JavaDoc для TypeToken.

Это решение работает для ограниченного набора случаев, как и ответ @noah с отражением. Я попробовал их все сегодня ... И закончил тем, что передал экземпляр класса параметров параметризованному классу (чтобы иметь возможность вызывать .newInstance ()). Очень большой недостаток "дженериков" ... new Foo <Bar> (Bar.class); ... class Foo <T> {закрытый final Class <T> mTFactory; Foo (Класс <T> tClass) {mTFactory = tClass; ...} T instance = tFactory.newInstance (); }

yvolk 01.03.2015 22:36

это работает во всех случаях, даже статические фабричные методы, которые принимают общие параметры

user177800 19.02.2016 01:35

Улучшение ответа @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
}

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

Lorne Laliberte 23.04.2015 10:36

Или, может быть, чтобы преодолеть захват Exception, используйте вместо него Supplier<E>.

Martin D 01.06.2018 16:07

К сожалению, 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);

Не могли бы вы сказать мне, почему вы голосуете против, когда делаете это? Я не понимаю, почему официальный обходной путь - плохое решение. Спасибо.

Neepsnikeep 30.08.2017 09:59

Думаю, у вас меньше голосов, потому что ваш ответ по сути такой же, как и у Джастина Радда: stackoverflow.com/a/75254/103412

Torsten 18.03.2019 13:37

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

Хороший. Это позволяет избежать размышлений и обработки исключений.

Bruno Leite 07.06.2017 19:14

Так мило. К сожалению для пользователей Android, для этого требуется уровень API 24 или выше.

Michael Updike 23.01.2018 11:43

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

Holger 10.01.2019 12:05

Можно ли было поставить только SomeContainer stringContainer = new SomeContainer(String::new);?

Aaron Franke 22.01.2019 05:03

@AaronFranke: Нет, потому что тогда вы будете использовать необработанный тип.

Daniel Pryden 22.01.2019 16:36

Вот улучшенное решение, основанное на 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();
    }
}

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

что ты можешь сделать, это -

  1. Сначала объявите переменную этого универсального класса

    2.Затем создайте из него конструктор и создайте экземпляр этого объекта.

  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»

Daniel Methner 16.11.2020 09:16

Используйте класс TypeToken<T>:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

Если вы используете Guava вместо GSON, все будет немного иначе: (T) new TypeToken<T>(getClass()){}.getRawType().newInstance();

Jacob van Lingen 16.10.2019 12:44

Другие вопросы по теме