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

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

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

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

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

Ответ 1

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

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

Это боль. Но это работает. Обертка его в заводской схеме делает ее более переносимой.

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

}

Ответ 6

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

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

Плюсы:

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

Минусы:

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

Ответ 7

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

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.

Ответ 8

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

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}

Ответ 9

Из Учебник по 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);
}

Вы можете вызвать метод append следующим образом:

List<String> ls = new ArrayList<>();
append(ls, String.class);

Ответ 10

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

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

Ответ 11

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

new SomeContainer<SomeType>(SomeType.class);

Вы можете использовать метод factory:

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

}

Ответ 12

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

Ответ 13

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

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

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

Ответ 14

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

Ответ 15

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

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

Ответ 16

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

Возможно, кто-то может исправить:

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

Ответ 17

Вставка ответа @Noah.

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

a] Безопаснее, если используется более 1 общий тип, если вы изменили порядок.

b] Время от времени меняется сигнатура общего типа класса, поэтому вы не будете удивлены необъяснимыми исключениями во время выполнения.

Надежный код

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

Ответ 18

Надеюсь, что это не слишком поздно, чтобы помочь!

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

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

Ответ 19

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

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

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

  2. Затем используйте его там, где вы хотите его использовать

example-

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

Ответ 20

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

Ответ 21

Если вы имеете в виду 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++;    
  }
}

Ответ 22

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

Ответ 23

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

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

Ответ 24

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

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

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

Ответ 25

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

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

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

class SomeStringContainer extends SomeContainer<String>

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

Ответ 26

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

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

Ответ 27

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