Create instance of generic type in Java?

后端 未结 27 3077
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-21 06:14

Is it possible to create an instance of a generic type in Java? I\'m thinking based on what I\'ve seen that the answer is no (due to type erasure), but

相关标签:
27条回答
  • 2020-11-21 06:45

    If you mean new E() then it is impossible. And I would add that it is not always correct - how do you know if E has public no-args constructor? But you can always delegate creation to some other class that knows how to create an instance - it can be Class<E> or your custom code like this

    interface Factory<E>{
        E create();
    }    
    
    class IntegerFactory implements Factory<Integer>{    
      private static int i = 0; 
      Integer create() {        
        return i++;    
      }
    }
    
    0 讨论(0)
  • 2020-11-21 06:46

    An imporovement of @Noah's answer.

    Reason for Change

    a] Is safer if more then 1 generic type is used in case you changed the order.

    b] A class generic type signature changes from time to time so that you will not be surprised by unexplained exceptions in the runtime.

    Robust Code

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

    Or use the one liner

    One Line Code

    model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
    
    0 讨论(0)
  • 2020-11-21 06:47

    Here is an improved solution, based on ParameterizedType.getActualTypeArguments, already mentioned by @noah, @Lars Bohl, and some others.

    First small improvement in the implementation. Factory should not return instance, but a type. As soon as you return instance using Class.newInstance() you reduce a scope of usage. Because only no-arguments constructors can be invoke like this. A better way is to return a type, and allow a client to choose, which constructor he wants to invoke:

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

    Here is a usage examples. @Lars Bohl has shown only a signe way to get reified geneneric via extension. @noah only via creating an instance with {}. Here are tests to demonstrate both cases:

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

    Note: you can force the clients of TypeReference always use {} when instance is created by making this class abstract: public abstract class TypeReference<T>. I've not done it, only to show erased test case.

    0 讨论(0)
  • 2020-11-21 06:48

    From Java Tutorial - Restrictions on Generics:

    Cannot Create Instances of Type Parameters

    You cannot create an instance of a type parameter. For example, the following code causes a compile-time error:

    public static <E> void append(List<E> list) {
        E elem = new E();  // compile-time error
        list.add(elem);
    }
    

    As a workaround, you can create an object of a type parameter through reflection:

    public static <E> void append(List<E> list, Class<E> cls) throws Exception {
        E elem = cls.newInstance();   // OK
        list.add(elem);
    }
    

    You can invoke the append method as follows:

    List<String> ls = new ArrayList<>();
    append(ls, String.class);
    
    0 讨论(0)
  • 2020-11-21 06:48

    There are various libraries that can resolve E for you using techniques similar to what the Robertson article discussed. Here's an implemenation of createContents that uses TypeTools to resolve the raw class represented by E:

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

    This assumes that getClass() resolves to a subclass of SomeContainer and will fail otherwise since the actual parameterized value of E will have been erased at runtime if it's not captured in a subclass.

    0 讨论(0)
  • 2020-11-21 06:48

    You can with a classloader and the class name, eventually some parameters.

    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);
    
    0 讨论(0)
提交回复
热议问题