Create instance of generic type in Java?

后端 未结 27 3140
佛祖请我去吃肉
佛祖请我去吃肉 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: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 {
      public Class 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) 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 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 p = new TypeReference(){};
        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{}
    
      @Test
      public void reifiedExtenension() throws Exception {
        TypeReference 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. I've not done it, only to show erased test case.

提交回复
热议问题