626

是否可以在 Java 中创建泛型类型的实例?根据我所看到的,我认为答案是no由于类型擦除),但如果有人能看到我遗漏的东西,我会很感兴趣:

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

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

我将把这个开放一段时间,看看是否有人提出与 Ian Robertson 的Artima 文章截然不同的东西。

4

29 回答 29

361

你是对的。你不能这样做new E()。但是您可以将其更改为

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

这是一种痛苦。但它有效。将其包装在工厂模式中使其更容易忍受。

于 2008-09-16T18:10:01.820 回答
140

在 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构造函数引用

如果您的构造函数接受参数,则可以使用 lambda 表达式:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
于 2016-03-30T16:51:40.597 回答
139

我不知道这是否有帮助,但是当您子类化(包括匿名)泛型类型时,类型信息可通过反射获得。例如,

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

但这是很多工作,并且只适用于子类。不过可以很方便。

于 2008-09-16T18:18:29.880 回答
89

您将需要某种抽象工厂来将责任转嫁给:

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();
    }
}
于 2008-09-16T18:36:12.570 回答
26
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());
    }

}
于 2010-09-03T12:41:57.437 回答
24

如果你需要一个泛型类中类型参数的新实例,那么让你的构造函数要求它的类......

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

优点:

  • 比罗伯逊的超级类型令牌 (STT) 方法简单得多(而且问题更少)。
  • 比 STT 方法(早餐会吃掉你的手机)效率更高。

缺点:

  • 无法将 Class 传递给默认构造函数(这就是为什么 Foo 是最终的)。如果你真的需要一个默认构造函数,你总是可以添加一个 setter 方法,但你必须记得稍后再给她一个电话。
  • 罗伯逊的反对意见... 比害群之马更多的酒吧(尽管再次指定类型参数类不会完全杀死你)。与罗伯逊的说法相反,这并不违反 DRY 原则,因为编译器将确保类型正确性。
  • 不完全Foo<L>证明。对于初学者...newInstance()如果类型参数类没有默认构造函数,将抛出一个摆动器。无论如何,这确实适用于所有已知的解决方案。
  • 缺乏对 STT 方法的完全封装。不过没什么大不了的(考虑到 STT 的惊人性能开销)。
于 2013-01-07T07:11:23.823 回答
23

您现在可以执行此操作,并且不需要一堆反射代码。

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

当然,如果您需要调用需要一些反射的构造函数,但这是有据可查的,这个技巧不是!

这是TypeToken 的 JavaDoc

于 2014-08-08T02:03:27.300 回答
16

来自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.getDeclaredConstructor().newInstance();   // OK
    list.add(elem);
}

您可以按如下方式调用 append 方法:

List<String> ls = new ArrayList<>();
append(ls, String.class);
于 2012-09-13T13:12:51.680 回答
15

考虑一种更实用的方法:不要从无到有地创建一些 E(这显然是代码味道),而是传递一个知道如何创建 E 的函数,即

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
于 2015-04-16T16:20:44.810 回答
9

当您在编译时使用 E 时,您并不真正关心实际的泛型类型“E”(您使用反射或使用泛型类型的基类),因此让子类提供 E 的实例。

abstract class SomeContainer<E>
{
    abstract protected E createContents();
    public void doWork(){
        E obj = createContents();
        // Do the work with E 
     }
}

class BlackContainer extends SomeContainer<Black>{
    protected Black createContents() {
        return new Black();
    }
}
于 2014-02-12T01:31:56.797 回答
7

这是我想出的一个选项,它可能会有所帮助:

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();
}
于 2008-09-16T18:14:49.030 回答
7

如果您不想在实例化过程中键入两次类名,例如:

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

}
于 2008-09-17T20:19:51.860 回答
6

不幸的是,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);
}

您可以按如下方式调用 append 方法:

List<String> ls = new ArrayList<>();
append(ls, String.class);
于 2016-02-10T12:32:49.873 回答
5

您可以使用:

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

但是您需要提供确切的类名,包括包,例如。java.io.FileInputStream. 我用它来创建一个数学表达式解析器。

于 2008-12-17T22:09:03.157 回答
5

希望这还为时不晚!!!

Java 是类型安全的,这意味着只有对象才能创建实例。

就我而言,我无法将参数传递给该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();
    }
}

如果您使用无对象类型扩展泛型类,则使用反射会创建运行时错误。要将您的泛型类型扩展为对象,请将此错误转换为编译时错误。

于 2019-01-16T09:07:24.467 回答
4

使用TypeToken<T>类:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}
于 2019-07-20T14:03:07.733 回答
3

我以为我可以做到,但很失望:它不起作用,但我认为它仍然值得分享。

也许有人可以纠正:

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 的解决方案

于 2013-01-03T20:13:41.280 回答
3

@Noah 回答的改进。

变化的原因

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();
于 2014-11-07T08:13:49.170 回答
3

你能做的是——

  1. 首先声明该泛型类的变量

    2.然后创建它的构造函数并实例化该对象

  2. 然后在任何你想使用的地方使用它

例子-

1

private Class<E> entity;

2

public xyzservice(Class<E> entity) {
        this.entity = entity;
    }



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

3.

E e = getEntity(实体);

于 2019-07-19T12:11:23.903 回答
1

这是一个createContents使用TypeTools来解析由 表示的原始类的实现E

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

此方法仅在子类化时才有效,因此在类型定义中捕获了SomeContainer的实际值:E

class SomeStringContainer extends SomeContainer<String>

否则 E 的值在运行时被擦除并且不可恢复。

于 2014-12-04T20:09:34.057 回答
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 );
   }
}
于 2012-04-06T11:26:04.183 回答
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++;    
  }
}
于 2008-09-16T18:42:58.653 回答
0

正如您所说,由于类型擦除,您无法真正做到这一点。您可以使用反射来完成它,但它需要大量代码和大量错误处理。

于 2008-09-16T18:06:22.733 回答
0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
于 2011-03-22T09:59:50.937 回答
0

有各种库可以E使用类似于 Robertson 文章讨论的技术为您解决问题。这是一个createContents使用TypeTools来解析由 E 表示的原始类的实现:

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

这假设 getClass() 解析为 SomeContainer 的子类,否则将失败,因为如果 E 的实际参数化值未在子类中捕获,它将在运行时被擦除。

于 2014-06-11T23:42:38.887 回答
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)
    }
于 2021-05-03T05:23:42.350 回答
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 仅展示了一种通过扩展获得具体化的通用方法。@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>

于 2018-12-28T07:53:39.290 回答
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在派生类中实现方法(或者不实现它以防您不需要它。

于 2021-06-16T11:05:19.993 回答
-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);
于 2014-02-04T13:00:48.447 回答