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

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

Create instance of generic type in Java?

是否可以在Java中创建泛型类型的实例?我的想法是基于我所看到的答案是no(由于类型删除),但如果有人能看到我丢失的东西,我会感兴趣的:

1
2
3
4
5
6
7
class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

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

我会把这个打开一段时间,看看是否有人想出任何明显不同于伊恩罗伯逊的Artima文章。


你是对的。你不能做new E()。但是你可以把它改成

1
2
3
4
5
private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

这是一种痛苦。但它有效。用工厂的方式包装它会让它更容易接受。


不知道,如果这有帮助,但是当您对一个泛型类型进行子类(包括匿名)时,类型信息可以通过反射获得。例如。,

1
2
3
4
5
6
7
8
9
10
11
public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

因此,当您将foo子类化时,会得到一个bar的实例,例如,

1
2
// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

但是这是一个很大的工作,而且只适用于子类。不过也很方便。


在Java 8中,您可以使用EDCOX1 6功能接口来实现这一点:

1
2
3
4
5
6
7
8
9
10
11
class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

您可以这样构造这个类:

1
SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

该行的语法String::new是一个构造函数引用。

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

1
2
SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

你需要某种抽象的工厂来把责任转嫁给:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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());
    }

}

如果需要泛型类内类型参数的新实例,则使构造函数要求其类…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
        ...
    }
}

用途:

1
2
Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

赞成的意见:

  • 比罗伯逊的超类型令牌(STT)方法简单得多(问题也更少)。
  • 比STT方法更有效(它会在早餐时吃掉你的手机)。

欺骗:

  • 无法将类传递给默认构造函数(这就是foo是final的原因)。如果您确实需要一个默认的构造函数,您可以始终添加一个setter方法,但之后必须记住给她一个调用。
  • 罗伯逊反对…更多的条比一个黑绵羊(尽管再次指定类型参数类不会完全杀死您)。与罗伯逊的说法相反,这无论如何都不会违反dry原则,因为编译器将确保类型的正确性。
  • 不完全是Foo证据。首先……如果类型参数类没有默认的构造函数,newInstance()将抛出一个摇摆器。不过,这确实适用于所有已知的解决方案。
  • 缺乏STT方法的完全封装。不过没什么大不了的(考虑到STT的惊人的性能开销)。

您现在可以这样做,它不需要一堆反射代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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。


考虑一个更实用的方法:不要凭空创建一些e(这显然是一种代码味道),而是传递一个知道如何创建e的函数,即。

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

从Java教程-对泛型的限制:

无法创建类型参数的实例

无法创建类型参数的实例。例如,以下代码会导致编译时错误:

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

作为解决方法,您可以通过反射创建类型参数的对象:

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

您可以调用append方法,如下所示:

1
2
List<String> ls = new ArrayList<>();
append(ls, String.class);


如果不想在实例化期间键入两次类名,如:

1
new SomeContainer<SomeType>(SomeType.class);

您可以使用工厂方法:

1
<E> SomeContainer<E> createContainer(Class<E> class);

喜欢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();
    }

}

以下是我提出的一个选项,它可能有助于:

1
2
3
4
5
6
7
8
9
10
11
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的实例):

1
2
3
4
@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}

JAVA不幸的是不允许你想做什么。参见官方解决方案:

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

1
2
3
4
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:

1
2
3
4
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:

1
2
List<String> ls = new ArrayList<>();
append(ls, String.class);

在编译时使用e时,实际上并不关心实际的泛型类型"e"(使用反射或使用泛型类型的基类),因此让子类提供e的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
    }
}

你可以使用:

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

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


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

也许有人可以纠正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()+"]");

    }
}

它产生:

1
2
3
4
5
6
7
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提供的解决方案


对诺亚回答的否定。

变更原因

如果在更改顺序的情况下使用多个泛型类型,则A]更安全。

B]类的泛型类型签名会不时更改,这样您就不会对运行时中无法解释的异常感到惊讶。

鲁棒代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
            }
        }
    }
}

或者用一个衬垫

单行代码

1
model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();

您可以通过以下代码片段来实现这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 );
   }
}

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

正如你所说,你不能真的去做,因为类型删除。您可以使用反射来完成它,但是它需要大量的代码和大量的错误处理。


有各种各样的库可以使用类似于Robertson文章讨论的技术为您解析E。下面是createContents的一个实现,它使用类型工具解析由e表示的原始类:

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

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


如果你是说new E()那是不可能的。我还要补充一点,这并不总是正确的——您如何知道e是否具有public no args构造函数?但是,您总是可以将创建委托给其他知道如何创建实例的类-它可以是Class或您的自定义代码,如下面所示

1
2
3
4
5
6
7
8
9
10
interface Factory<E>{
    E create();
}    

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

下面是createContents的一个实现,它使用typetools解析E表示的原始类:

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

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

1
class SomeStringContainer extends SomeContainer<String>

否则,e的值在运行时被擦除,不可恢复。


希望这不会太晚帮不上忙!!!!

Java是类型安全的,只有对象是能够创建实例的。

在我的例子中,我不能将参数传递给createContents方法。我的解决方案是使用扩展而不是所有下面的答案。

1
2
3
4
5
6
private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}

这是我的例子,我不能传递参数。

1
2
3
4
5
6
7
public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}

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


可以使用类加载器和类名,最后是一些参数。

1
2
3
4
5
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);


这里有一个改进的解决方案,基于ParameterizedType.getActualTypeArguments,已经被@noah、@lars bohl和其他一些人提到。

实施中的第一个小改进。工厂不应返回实例,而应返回类型。一旦您使用Class.newInstance()返回实例,就减少了使用范围。因为只有没有参数构造函数可以这样调用。更好的方法是返回一个类型,并允许客户机选择他要调用的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }

  }
}

下面是一个用法示例。@拉尔斯·波尔只展示了一种通过扩展获得身份化的通用性的签名方式。@noah只能通过使用{}创建一个实例。以下是演示这两种情况的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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创建实例时,可以强制TypeReference的客户机始终使用{}。我没有做,只是为了显示删除的测试用例。


推荐阅读