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

java generics

414819 просмотра

27 ответа

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

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

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

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

Автор: David Citron Источник Размещён: 20.07.2019 05:03

Ответы (27)


313 плюса

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

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

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

Автор: Justin Rudd Размещён: 16.09.2008 06:10

124 плюса

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

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

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

Автор: noah Размещён: 16.09.2008 06:18

97 плюса

В 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));
Автор: Daniel Pryden Размещён: 30.03.2016 04:51

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();
    }
}
Автор: Tom Hawtin - tackline Размещён: 16.09.2008 06:36

23 плюса

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

}
Автор: Lars Bohl Размещён: 03.09.2010 12:41

20 плюса

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

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 Размещён: 07.01.2013 07:11

19 плюса

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

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 .

Автор: user177800 Размещён: 08.08.2014 02:03

12 плюса

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

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
Автор: Ingo Размещён: 16.04.2015 04:20

8 плюса

Из Обучающего курса 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);
Автор: Sergiy Sokolenko Размещён: 13.09.2012 01:12

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();
}
Автор: Mike Stone Размещён: 16.09.2008 06:14

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

}
Автор: jb. Размещён: 17.09.2008 08:19

5 плюса

К сожалению, 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 Размещён: 10.02.2016 12:32

4 плюса

Когда вы работаете с 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();
    }
}
Автор: Ira Размещён: 12.02.2014 01:31

3 плюса

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

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

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

Автор: Jaroslav Smid Размещён: 17.12.2008 10:09

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

Автор: Luigi R. Viggiano Размещён: 03.01.2013 08:13

2 плюса

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

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

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();
Автор: Amio.io Размещён: 07.11.2014 08:13

0 плюса

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

Автор: Adam Rosenfield Размещён: 16.09.2008 06:06

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++;    
  }
}
Автор: Pavel Feldman Размещён: 16.09.2008 06:42

0 плюса

return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
Автор: Rachid Размещён: 22.03.2011 09:59

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 );
   }
}
Автор: bogdan Размещён: 06.04.2012 11:26

0 плюса

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

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

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

Автор: Jonathan Размещён: 11.06.2014 11:42

0 плюса

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

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

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

class SomeStringContainer extends SomeContainer<String>

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

Автор: Jonathan Размещён: 04.12.2014 08:09

0 плюса

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

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

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

Автор: Se Song Размещён: 16.01.2019 09:07

0 плюса

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

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

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

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

пример-

1

private Class<E> entity;

2

public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
        return entity.newInstance();
    }

3

E e = getEntity(entity);
Автор: Sudhanshu Jain Размещён: 19.07.2019 12:11

0 плюса

Используйте класс TypeToken. Например:

public class MyClass<T>{
  public T doSomething(){
  return (T) new TypeToken<T>(){}.getRawType().newInstance();
  }
}
Автор: cacheoff Размещён: 20.07.2019 02:03

-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);
Автор: Roald Размещён: 04.02.2014 01:00

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

Автор: Alexandr Размещён: 28.12.2018 07:53
Вопросы из категории :
32x32