在Java中创建泛型类型的实例?

651

在Java中,是否可以创建泛型类型的实例?根据我所见,由于类型擦除,答案是不行,但如果有人能发现我所忽略的情况,我很感兴趣:

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

编辑:事实证明超级类型标记可以用来解决我的问题,但需要大量基于反射的代码,正如下面的一些答案所指出的。

我会将其保持开放一段时间,看是否有人提出了与Ian Robertson的Artima文章截然不同的解决方法。


3
在Android设备上测试了性能,进行了10000次操作:使用new SomeClass()耗时8-9毫秒,使用Factory<SomeClass>.createInstance()耗时9-11毫秒,使用最短的反射方式SomeClass z = SomeClass.class.newInstance()耗时64-71毫秒。而且所有的测试都在单个try-catch块中完成。记得Reflection newInstance()会抛出4个不同的异常吗?所以我决定使用工厂模式。 - Deepscorn
另请参见:https://dev59.com/vnA75IYBdhLWcg3wKluM#5684761 - Dave Jarvis
4
在Java 8中,现在可以传递构造函数引用或lambda表达式,这使得解决这个问题变得相当容易。有关详细信息,请参见下面的我的回答 - Daniel Pryden
我认为编写这样的代码是个坏主意,有更加优雅和易读的方法来解决底层问题。 - Krzysztof Cichocki
2
“只是一会儿”,David Citron说道……从那时起已经过去了十一个年头…… - MC Emperor
@KrzysztofCichocki 通常是正确的,但有些情况不同。例如,当您想要为检查调用add(...)set(0,...)以检查是否为空的通用列表编写断言时。 - Egor Hans
29个回答

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

1
这在我的原始问题示例中不起作用。SomeContainer的超类只是Object。因此,this.getClass().getGenericSuperclass()返回一个Class(类java.lang.Object),而不是一个ParameterizedType。实际上,同行回答https://dev59.com/qnVD5IYBdhLWcg3wI3-L#5389482也已经指出了这一点。 - David Citron
1
完全错误:在主线程中出现异常 "main" java.lang.ClassCastException: java.lang.Class 无法转换为 java.lang.reflect.ParameterizedType - Aubin

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

4
完全错误:在线程“main”中的异常java.lang.ClassCastException:java.lang.Class无法转换为java.lang.reflect.ParameterizedType。 - Aubin

0

我受到Ira的解决方案的启发,并稍作修改。

abstract class SomeContainer<E>
{
    protected E createContents() {
        throw new NotImplementedException();
    }

    public void doWork(){
        E obj = createContents();
        // Do the work with E 
     }
}

class BlackContainer extends SomeContainer<Black>{
    // this method is optional to implement in case you need it
    protected Black createContents() {
        return new Black();
    }
}

如果您需要 E 实例,您可以在派生类中实现 createContents 方法(或者在不需要它的情况下将其保留未实现)。

0
请注意,在 Kotlin 中,通用类型可能没有默认构造函数。
 implementation("org.objenesis","objenesis", "3.2")

    val fooType = Foo::class.java
    var instance: T = try {
        fooType.newInstance()
    } catch (e: InstantiationException) {
//            Use Objenesis because the fooType class has not a default constructor
        val objenesis: Objenesis = ObjenesisStd()
        objenesis.newInstance(fooType)
    }

0

正如您所提到的,泛型无法实例化。在我看来,您需要改变设计并利用工厂方法设计模式。这样,您就不需要将类或方法设置为泛型:

class abstract SomeContainer{
    Parent execute(){
        return method1();
    }
    
    abstract Parent method1();
}


class Child1 extends Parent{
    Parent method1(){
        return new Parent();
    } 
} 

class Child2 extends Parent{
    Parent method1(){
        return new Child2();
    } 
} 

0

如果你指的是 new E(),那么这是不可能的。我还要补充一点,这并不总是正确的——你怎么知道 E 是否有公共无参构造函数? 但你可以将创建实例的责任委托给其他知道如何创建实例的类——它可以是Class<E>,也可以是你自己编写的类,就像这样。

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0

正如你所说,由于类型擦除的原因,你实际上无法真正做到。虽然可以使用反射来实现某种程度上的功能,但这需要大量的代码和错误处理。


你如何使用反射完成它?我只能看到Class.getTypeParameters()这个方法,但它只返回声明的类型而不是运行时类型。 - David Citron
你是在说这个吗?http://www.artima.com/weblogs/viewpost.jsp?thread=208860 - David Citron

0

这里是一个改进的解决方案,基于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只展示了一种通过扩展获取reified geneneric的方法,@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);
  }
}

注意:您可以通过将此类声明为抽象类:public abstract class TypeReference<T>,强制TypeReference的客户端在创建实例时始终使用{}。我没有这样做,只是为了展示被擦除的测试用例。


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

这比仅仅传递类对象还糟糕。 - newacct
你根本不需要显式地引用类加载器。 - Stefan Reich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接