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


576

Чи можливо створити екземпляр загального типу на Java? Я думаю, грунтуючись на тому, що я бачив, що відповідь є no( через стирання типу ), але мені було б цікаво, чи хтось може побачити щось, чого мені не вистачає:

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

EDIT: Виявляється, для вирішення моєї проблеми можна використовувати маркери Super Type , але для цього потрібно багато коду на основі роздумів, як показано в деяких відповідях нижче.

Я залишу це відкрите на деякий час, щоб побачити, чи хтось придумає щось кардинально інше, ніж стаття про артисту Ієна Робертсона .


2
Щойно перевірена продуктивність на пристрої Android. 10000 операцій і: 8-9 мс приймає новий SomeClass (), 9-11 мс займає Factory <SomeClass> .createInstance (), а 64-71 мс приймає найкоротші роздуми: SomeClass z = SomeClass.class.newInstance (). І всі тести проходили в одному блоці пробного лову. Reflection newInstance () кидає 4 різні винятки, пам’ятаєте? Тому я вирішив використовувати заводський візерунок
Deepscorn


4
З Java 8 ви тепер можете передавати посилання конструктора або лямбда, що робить цю проблему досить тривіальною для вирішення. Детальну інформацію див. У моїй відповіді нижче .
Даніель Приден

Я думаю, що це погана ідея писати такий код - це більш елегантні та читані способи вирішення основної проблеми.
Кшиштоф Чичоцький

1
@DavidCitron " ненадовго " він сказав ... З тих пір минуло одинадцять років ...
Імператор MC

Відповіді:


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
Так, це приємно, особливо якщо загальний клас абстрактний, це можна зробити в конкретних підкласах :)
П'єр Генрі

Цей метод також працює, якщо клас Fooне абстрактний. Але чому це працює лише на анонімних підкласах Foo? Припустимо, ми робимо Fooбетон (ми залишаємо його поза abstract), чому це new Foo<Bar>();призведе до помилки, а new Foo<Bar>(){};ні? (Виняток: "Клас не можна привласнити до ParameterizedType")
Тім Куйперс

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отримують щось, що є 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 або вище.
Майкл Updike

1
Зважаючи на меншу метушні та функціональний підхід, на мою думку, це буде прийнятою відповіддю
Томаш

2
… І це не відрізняється від цієї ще старшої відповіді, показуючи, що технічна схема, що стоїть за нею, навіть старша, ніж підтримка Java для лямбда-виразів та посилань методів, тоді як ви навіть можете використовувати цей старший код разом із ними, коли ви оновили свій компілятор…
Холгер

Чи можна було б поставити просто 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();
    }
}

..і як виглядає Factory.create ()?
OhadR

6
@OhadR Factory<>- це інтерфейс, тому немає тіла. Суть у тому, що вам потрібен шар непрямих для передачі долара методам, які "знають" необхідний код для побудови екземпляра. Це набагато краще робити це звичайним кодом, а не металінгвістичним, Classабо, Constructorякщо рефлексія приносить шкоду світові.
Том Хотін - тайклін

2
Тепер дні ви можете створити екземпляр фабрики з методом виразу посилання , як це:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Лії

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, якщо ви використовуєте в загальному родовому типі. Потім ви отримуєте аргумент фактичного типу. Ви повинні перевірити, чи він також є ParamterizedType, і якщо так, поверніть його RawType (або щось краще, ніж це). Інша проблема з цим полягає в тому, коли ми продовжуємо більше, ніж один раз цей код також викине ClassCastExeption.
Damian Leszczyński - Vash

3
Викликаний: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl не може бути приведений до java.lang.Class
JUAN

@ DamianLeszczyński-Vash також не вдасться, наприкладclass GenericHome<T> extends Home<T>{}
Holger

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()буде кидати воблер, якщо клас аргументу type не має конструктора за замовчуванням. Це все ж стосується всіх відомих рішень, хоча все одно.
  • Не вистачає загальної інкапсуляції підходу STT. Але це не велика справа (враховуючи надзвичайні показники витрат на НТТ).

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 ()). Дуже великий дефіцит "дженериків" ... новий Foo <Bar> (Bar.class); ... клас Foo <T> {приватний заключний клас <T> mTFactory; Foo (клас <T> tClass) {mTFactory = tClass; ...} T примірник = 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();
    }
}

EDIT: Або ви можете використовувати цей конструктор (але він вимагає екземпляра E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}

Так, це працює так само, навіть без дженериків - при дженериках екземпляр цього контейнера стає дещо зайвим (ви повинні вказати, що "Е" вдвічі).
Девід Цитрон

ну ось що трапляється, коли ви використовуєте 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

Я думаю, що ви
Torsten

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. Я використовував це для створення аналізатора математичних виразів.


15
І як отримати точну назву класу загального типу під час виконання?
Девід Цитрон

Вам потрібно буде зберегти його за допомогою екземпляра цього класу. Виконано, хоча й навряд чи зручно. Якщо у вашому родовому був член типу E (або T або будь-який інший), його двійкове ім'я просто foo.getClass().getName(). Звідки береться ЦЕ Екземпляр? Зараз я передаю його конструктору в проекті, над яким зараз працюю.
Марк Сторер

3

Сподіваюсь, це ще не пізно допоможе !!!

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

Використовуючи відображення, створюйте помилку часу виконання, якщо ви розширюєте свій загальний клас без жодного типу об'єкта. Щоб розширити ваш загальний тип, щоб об'єкт перетворив цю помилку, щоб скласти помилку часу.


3

Використовуйте 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

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

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 (сутність);


0

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


Як би ви це зробили, використовуючи рефлексію? Єдиний метод, який я бачу, це Class.getTypeParameters (), але він повертає лише оголошені типи, а не типи виконання.
Девід Цитрон

Ви про це говорите? artima.com/weblogs/viewpost.jsp?thread=208860
David Citron

0

Якщо ви маєте на увазі, new 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()повертає a 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>. Я цього не робив, лише щоб показати стертий тестовий випадок.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.