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

576

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

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

РЕДАКТИРОВАТЬ: Оказывается, супер токены типа могут быть использованы для решения моей проблемы, но это требует много кода на основе отражения, как указано в некоторых ответах ниже.

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

Дэвид Цитрон
источник
2
Только что протестировал производительность на Android устройстве. 10000 операций и: 8–9 мс занимает новый SomeClass (), 9–11 мс - Factory <SomeClass> .createInstance (), а 64–71 мс - кратчайшее отражение: SomeClass z = SomeClass.class.newInstance (). И все тесты были в одном блоке try-catch. Отражение newInstance () выдает 4 разных исключения, помните? Поэтому я решил использовать фабричный шаблон
Deepscorn
См. Также: stackoverflow.com/a/5684761/59087
Дейв Джарвис
4
В Java 8 теперь вы можете передавать ссылку на конструктор или лямбду, что делает эту проблему довольно простой для обхода. Смотрите мой ответ ниже для деталей.
Даниэль Приден
Я думаю, что писать такой код - плохая идея, так как есть более элегантные и удобочитаемые способы решения данной проблемы.
Кшиштоф
1
@DavidCitron "ненадолго", сказал он ... С тех пор прошло одиннадцать лет ...
MC Emperor

Ответы:

332

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

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

Это боль. Но это работает. Заворачивание в фабричный шаблон делает его более терпимым.

Джастин Радд
источник
11
Да, я видел это решение, но оно работает, только если у вас уже есть ссылка на объект Class того типа, который вы хотите создать.
Дэвид Цитрон
11
Да, я знаю. Было бы неплохо, если бы вы могли делать E.class, но это просто дает вам Object.class из-за стирания :)
Джастин Радд
6
Это правильный подход к этой проблеме. Обычно это не то, что вы хотите, но это то, что вы получаете.
Иоахим Зауэр
38
А как вы вызываете метод createContents ()?
Алексис Дафреной
8
Это больше не единственный способ сделать это, теперь есть лучший способ, который не требует передачи Class<?>ссылки с использованием Guava и TypeToken, посмотрите этот ответ для кода и ссылок!
129

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

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

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

Ной
источник
2
Да, это хорошо, особенно если обобщенный класс является абстрактным, вы можете сделать это в конкретных подклассах :)
Pierre Henry
Этот метод также работает, если класс Fooне абстрактный. Но почему он работает только на анонимных подклассах Foo? Предположим, что мы делаем Fooконкретный (мы исключаем abstract), почему это new Foo<Bar>();приведет к ошибке, а new Foo<Bar>(){};нет? (Исключение: «Класс не может быть приведен к параметризованному типу»)
Тим Кейперс
2
@TimKuipers <E>в class Foo<E>не связан с каким - либо конкретным типа. Вы увидите исключительное поведение , когда Eне статически связан, как в: new Foo<Bar>(), new Foo<T>() {...}, или class Fizz <E> extends Foo<E>. Первый случай не является статически связанным, он стирается во время компиляции. Второй случай заменяет другую переменную типа (T) вместо, Eно все еще не связана. И в последнем случае должно быть очевидно, что Eвсе еще не связан.
Уильям Прайс
4
Примером статической привязки параметра типа может быть class Fizz extends Foo<Bar>- в этом случае пользователи Fizzполучают что-то, что является a, Foo<Bar>и не может быть ничем, кроме a Foo<Bar>. Поэтому в этом случае компилятор с радостью закодирует эту информацию в метаданные класса Fizzи сделает ее доступной в виде ParameterizedTypeкода для отражения. Когда вы создаете анонимный внутренний класс, new Foo<Bar>() {...}он делает то же самое, за исключением того, что вместо Fizzкомпилятора генерируется «анонимное» имя класса, которое вы не узнаете, пока внешний класс не будет скомпилирован.
Уильям Прайс
4
Следует отметить, что это не будет работать, если аргументы типа также являются ParameterizedType. Например, Foo<Bar<Baz>>. Вы будете создавать экземпляр, ParameterizedTypeImplкоторый не может быть создан явно. Таким образом, это хорошая идея, чтобы проверить, getActualTypeArguments()[0]возвращает ли ParameterizedType. Если это так, то вы хотите получить необработанный тип и создать его экземпляр.
раздавить
106

В 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));
Даниэль Приден
источник
6
Неплохо. Это позволяет избежать размышлений и необходимости рассматривать исключения.
Бруно Лейте
2
Так мило. К сожалению для пользователей Android, это требует API уровня 24 или выше.
Майкл Апдайк
1
В соответствии с меньшим количеством суеты и функционального подхода это должно быть принято, по моему мнению,
Томас
2
… И он не отличается от этого даже более старого ответа, показывающего, что технический шаблон, стоящий за ним, даже старше, чем поддержка Java для лямбда-выражений и ссылок на методы, в то время как вы даже можете использовать этот старый код с ними после обновления вашего компилятора…
Holger
Можно ли будет просто поставить SomeContainer stringContainer = new SomeContainer(String::new);?
Аарон Франке
87

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

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();
    }
}
Том Хотин - Tackline
источник
..и как выглядит Factory.create ()?
OhadR
6
@OhadR Factory<>- это интерфейс, поэтому здесь нет тела. Дело в том, что вам нужен слой косвенных указаний, чтобы передать методы методам, которые «знают» необходимый код для создания экземпляра. Гораздо лучше делать это с помощью нормального кода, а не металингвистического Classили Constructorпотому, что рефлексия приносит целый мир боли.
Том Хотин - tackline
2
Теперь вы можете создать экземпляр фабрики с помощью ссылки на метод, например:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii
24
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());
    }

}
Ларс Бол
источник
3
Хороший подход к этому коду может вызвать ClassCastException, если вы используете в универсальном универсальном типе. Затем Вы извлекаете аргумент actualType. Вам следует проверить, что это также ParamterizedType и, если это так, вернуть его RawType (или что-то лучше этого). Другая проблема заключается в том, что когда мы расширяем более одного раза, этот код также генерирует ClassCastExeption.
Дамиан Лещинский - Ваш
3
Вызванный: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl не может быть приведен к java.lang.Class
JUAN
@ DamianLeszczyński-Vash также потерпит неудачу, напримерclass GenericHome<T> extends Home<T>{}
Хольгер
22

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

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

Плюсы:

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

Минусы:

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

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

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 .

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

Подумайте о более функциональном подходе: вместо создания некоторого E из ничего (что явно является запахом кода), передайте функцию, которая знает, как ее создать, т.е.

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
Инго
источник
6
Технически вы не передаете функцию, вы передаете функциональный объект (также известный как функтор ).
Лорн Лалиберте
3
Или , может быть , чтобы преодолеть ловя Exceptionиспользовать Supplier<E>вместо этого.
Мартин Д
10

Из Обучающего курса 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);
Сергей Соколенко
источник
6

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

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 и дженерики ... они не красивые, и есть серьезные ограничения ...
Майк Стоун
6

Если вы не хотите вводить имя класса дважды во время создания экземпляра, как в:

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

}
ДБ.
источник
6

К сожалению, 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);
Neepsnikeep
источник
Не могли бы вы сказать мне, почему вы отрицаете, когда вы делаете это? Я не понимаю, почему официальный обходной путь - плохое решение. Спасибо.
Neepsnikeep
Я полагаю, что вы проиграли голоса, потому что ваш ответ по сути такой же, как и у Джастина Радда: stackoverflow.com/a/75254/103412
Торстен
5

Когда вы работаете с E во время компиляции, вы на самом деле не заботитесь о фактическом универсальном типе «E» (либо используете отражение, либо работаете с базовым классом универсального типа), поэтому пусть подкласс предоставляет экземпляр E.

Abstract class SomeContainer<E>
{

    abstract protected  E createContents();
    public doWork(){
        E obj = createContents();
        // Do the work with E 

     }
}


BlackContainer extends SomeContainer<Black>{
    Black createContents() {
        return new  Black();
    }
}
Айра
источник
4

Ты можешь использовать:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

Но вам нужно указать точное имя класса, включая пакеты, например. java.io.FileInputStream, Я использовал это для создания парсера математических выражений.

hichris123
источник
15
И как вы получаете точное имя класса универсального типа во время выполнения?
Дэвид Цитрон
Вы должны сохранить его, используя экземпляр этого класса. Выполнимо, хотя вряд ли удобно. Если ваш универсальный элемент имел член типа E (или T или любой другой), получить его двоичное имя просто foo.getClass().getName(). Откуда этот экземпляр? В настоящее время я передаю один в конструктор в проекте, над которым я сейчас работаю.
Марк Сторер
3

Надеюсь, что еще не поздно помочь!

Java - это безопасность типов, только Object может создать экземпляр.

В моем случае я не могу передать параметры в 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();
    }
}

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

Се Сонг
источник
3

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

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}
cacheoff
источник
Если вы используете Гуаву вместо GSON, это немного по-другому:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Джейкоб ван Линген
2

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

Может быть, кто-то может исправить:

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

Луиджи Р. Виджано
источник
2

Улучшение ответа @ Ноа.

Причина изменения

а] Безопаснее, если используется более 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();
Amio.io
источник
2

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

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

Судханшу Джайн
источник
0

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

Адам Розенфилд
источник
Как бы вы сделали это, используя отражение? Единственный метод, который я вижу, это Class.getTypeParameters (), но он возвращает только объявленные типы, а не типы времени выполнения.
Дэвид Цитрон
Вы говорите об этом? artima.com/weblogs/viewpost.jsp?thread=208860
Дэвид Цитрон
0

Если вы имеете в виду, new E() то это невозможно. И я бы добавил, что это не всегда правильно - как узнать, есть ли у E открытый конструктор no-args? Но вы всегда можете делегировать создание другому классу, который знает, как создать экземпляр - это может быть Class<E>или ваш собственный код, подобный этому

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}
Павел Фельдман
источник
0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
Рашид
источник
1
Это не работает в моем примере в оригинальном вопросе. Суперкласс для SomeContainerэто просто Object. Следовательно, this.getClass().getGenericSuperclass()возвращает Class(класс java.lang.Object), а не a ParameterizedType. На самом деле это уже было указано коллегиальным ответом stackoverflow.com/questions/75175/… .
Дэвид Цитрон
1
Совершенно неправильно: исключение в потоке "main" java.lang.ClassCastException: java.lang.Class не может быть приведен к java.lang.reflect.ParameterizedType
Aubin
0

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

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 );
   }
}
Богдан Велиску
источник
4
Совершенно неправильно: исключение в потоке "main" java.lang.ClassCastException: java.lang.Class не может быть приведен к java.lang.reflect.ParameterizedType
Aubin
0

Существуют различные библиотеки, которые можно решить E, используя методы, аналогичные тем, которые обсуждались в статье Робертсона. Вот реализация, createContentsкоторая использует TypeTools для разрешения необработанного класса, представленного E:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Это предполагает, что getClass () преобразуется в подкласс SomeContainer и в противном случае завершится ошибкой, поскольку фактическое параметризованное значение E будет удалено во время выполнения, если оно не будет записано в подкласс.

Джонатан
источник
0

Вот реализация, createContentsкоторая использует TypeTools для разрешения необработанного класса, представленного E:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Этот подход работает, только если SomeContainerон разделен на подклассы, поэтому фактическое значение Eфиксируется в определении типа:

class SomeStringContainer extends SomeContainer<String>

В противном случае значение E стирается во время выполнения и не подлежит восстановлению.

Джонатан
источник
-1

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

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
Вам вообще не нужно явно ссылаться на загрузчик классов.
Стефан Райх
-1

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

Александр
источник