it-swarm.cn

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

是否可以在Java中创建泛型类型的实例?我正在考虑基于我所看到的答案是no 由于类型擦除 ),但如果有人能看到我缺少的东西,我会感兴趣:

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

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

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

533
David Citron

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

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

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

310
Justin Rudd

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

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

但这是很多工作,只适用于子类。虽然可以派上用场。

121
noah

在Java 8中,您可以使用 Supplier functional接口来实现这一点:

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));
92
Daniel Pryden

你需要某种抽象的工厂来传递:

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();
    }
}
87
Tom Hawtin - tackline
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());
    }

}
23
Lars Bohl

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

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

优点:

  • 比Robertson的超级类型令牌(STT)方法简单得多(并且问题少)。
  • 比STT方法(早餐会吃你的手机)更有效率。

缺点:

  • 无法将Class传递给默认构造函数(这就是为什么Foo是最终的)。如果你真的需要一个默认的构造函数,你总是可以添加一个setter方法,但是你必须记得稍后给她打个电话。
  • Robertson的反对意见......比黑羊更多的酒吧(虽然再次指定类型参数类不会完全杀死你)。与Robertson的说法相反,这并不违反DRY principal,因为编译器将确保类型正确性。
  • 不完全Foo<L>proof。对于初学者......如果类型参数类没有默认构造函数,newInstance()将抛出一个摇摆器。尽管如此,这确实适用于所有已知的解决方案。
  • 缺乏STT方法的完全封装。虽然没什么大不了的(考虑到STT的惊人性能开销)。
20
R2D2M2

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

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

18
user177800

想想一个更实用的方法:不是从无中创建一些E(这显然是代码气味),而是传递一个知道如何创建一个E的函数,即.

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

来自 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);
8
Sergiy Sokolenko

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

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

如果你不希望在实例化过程中两次输入类名,如:

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

}
6
jb.

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

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

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

您可以使用:

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

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

3
Jaroslav Smid

@诺亚的回答是一种推动力。

改变的理由

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();
2
Amio.io

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

也许有人可以纠正:

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

2
Luigi R. Viggiano

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

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

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

0
Jonathan

如果你的意思是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
Pavel Feldman
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
0
Rachid

您可以使用以下代码段实现此目的:

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

这是createContents的一个实现,它使用 TypeTools 来解析E所代表的原始类:

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

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

class SomeStringContainer extends SomeContainer<String>

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

0
Jonathan

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

0
Adam Rosenfield

希望这不是太晚了!

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

在我的情况下,我不能将参数传递给createContents方法。我的解决方案是使用extends而不是所有下面的答案。

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

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

0
Se Song

这是一个改进的解决方案,基于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的客户端在创建实例时始终使用{},方法是将这个类设为abstract:public abstract class TypeReference<T>。我没有这样做,只是为了显示已删除的测试用例。

0
Alexandr