it-swarm-ru.tech

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

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

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

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

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

538
David Citron

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

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

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

305
Justin Rudd

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

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

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

121
noah

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

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();
    }
}
86
Tom Hawtin - tackline

В 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));
86
Daniel Pryden
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());
    }

}
23
Lars Bohl

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

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>proof. Для начала ... newInstance() сгенерирует воблер, если класс аргумента типа не имеет конструктора по умолчанию. Это в любом случае относится ко всем известным решениям.
  • Отсутствует полная инкапсуляция подхода STT. Хотя это не имеет большого значения (учитывая огромные потери производительности STT).
20
R2D2M2

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

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 .

19
user177800

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

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

Из Учебное пособие по 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);
8
Sergiy Sokolenko

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

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

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

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
jb.

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

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

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

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

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

3
Jaroslav Smid

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

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

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();
2
Amio.io

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

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

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
Luigi R. Viggiano

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

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

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

0
Jonathan

Если вы имеете в виду 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
Pavel Feldman
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
0
Rachid

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

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

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

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

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

class SomeStringContainer extends SomeContainer<String>

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

0
Jonathan

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

0
Adam Rosenfield

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

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

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

0
Se Song

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

0
Alexandr