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

Dav*_*ron 554 java generics

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

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Jus*_*udd 325

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

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 你如何调用createContents()方法? (34认同)
  • 是的,我看到了这个解决方案,但它只有在您已经引用了要实例化的类型的Class对象时才有效. (11认同)
  • 是的,我知道.如果你可以做E.class但是因为擦除而只是给你Object.class会很好:) (11认同)
  • **这不再是唯一的方法,现在有一种更好的方法,不需要使用Guava和TypeToken传入`Class <?>`引用,[请参阅此代码和链接的答案!] (http://stackoverflow.com/a/25195050/177800)** (8认同)
  • 这是解决这个问题的正确方法.它通常不是你想要的,但它是你得到的. (6认同)
  • @Alexis Dufrenoy - 迟到但完整性......`sc = new SomeContainer <String>(); sc.createContents(String.class);`.至于它的价值,据我所知,这是对OP有用的唯一答案.我和其他人一起玩过但是`ClassCastException从`Type`转换为`Class`或者只能从参数化类本身看到`<T>`.有兴趣知道其他人是否有具体的资格.+1给这个并且会对我的其他人嗤之以鼻......我错过了什么? (3认同)
  • 该解决方案有效,但实际上它被标记为已弃用。它应该被替换为: clazz.getDeclaredConstructor().newInstance() (3认同)

noa*_*oah 124

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

public abstract class Foo<E> {

  public E instance;  

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

}
Run Code Online (Sandbox Code Playgroud)

所以,当你继承Foo时,你得到一个Bar实例,例如,

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );
Run Code Online (Sandbox Code Playgroud)

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

  • 静态绑定类型参数的一个例子是`类Fizz extends Foo <Bar>` - 在这种情况下,`Fizz`的用户得到的东西是`Foo <Bar>`并且不能只是一个`Foo <酒吧>`.因此,在这种情况下,编译器很乐意将该信息编码为`Fizz`的类元数据,并将其作为`ParameterizedType`提供给反射代码.当你创建一个像`new Foo <Bar>(){...}`这样的匿名内部类时,它会做同样的事情,除了代替`Fizz`,编译器会生成一个你不会知道的"匿名"类名直到外部类被编译. (4认同)
  • 应该注意,如果类型参数也是ParameterizedType,这将不起作用.例如,`Foo <Bar <Baz >>`.您将创建一个无法显式创建的`ParameterizedTypeImpl`实例.因此,检查`getActualTypeArguments()[0]`是否返回`ParameterizedType`是个好主意.如果是,那么您希望获取原始类型,并创建该实例. (4认同)
  • 是的,这很好,特别是如果泛型类是抽象的,你可以在具体的子类中做到这一点:) (2认同)
  • @TimKuipers`类Foo <E>`中的`<E>`没有绑定到任何特定类型.每当`E`不是*静态*绑定时,你会看到异常行为,如:`new Foo <Bar>()`,`new Foo <T>(){...}`或`class Fizz < E>扩展Foo <E>`.第一种情况不是静态绑定,它在编译时被*擦除*.第二种情况用另一种类型变量(T)代替"E"代替,但仍未结合.在最后一种情况下,显然"E"仍然是未绑定的. (2认同)

Dan*_*den 101

在Java 8中,您可以使用Supplier功能界面轻松实现此目的:

class SomeContainer<E> {
  private Supplier<E> supplier;

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

  E createContents() {
    return supplier.get();
  }
}
Run Code Online (Sandbox Code Playgroud)

你可以这样构造这个类:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);
Run Code Online (Sandbox Code Playgroud)

String::new行的语法是构造函数引用.

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

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
Run Code Online (Sandbox Code Playgroud)

  • 好的.它避免了反思,不得不对待异常. (5认同)
  • 太赞了。不幸的是,对于 Android 用户,这需要 API 级别 24 或更高级别。 (2认同)
  • …与[这个更老的答案](/sf/answers/5286991/)没什么不同,它表明其背后的技术模式甚至比Java对lambda表达式和方法引用的支持还要古老,尽管您甚至可以升级编译器后,请与该旧代码一起使用… (2认同)

Tom*_*ine 87

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @OhadR`Factory <>`是一个接口,因此没有正文.关键是你需要一层间接来将负担传递给那些"知道"构造实例所需代码的方法.使用普通代码而不是金属语言的"类"或"构造函数"来做这件事要好得多,因为反射会给整个世界带来伤害. (6认同)
  • 现在,您可以使用如下方法引用表达式创建工厂实例:`SomeContainer <SomeElement> cont = new SomeContainer <>(SomeElement :: new);` (2认同)

Lar*_*ohl 24

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

}
Run Code Online (Sandbox Code Playgroud)

  • 如果您在泛型中使用泛型类型,则此代码的良好方法可能会导致ClassCastException.然后你检索actualType参数你应该检查它是否也是ParamterizedType,如果是,则返回他的RawType(或者更好的东西).另一个问题是当我们扩展更多时,这个代码也将抛出ClassCastExeption. (3认同)
  • 引起:java.lang.ClassCastException:sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl无法强制转换为java.lang.Class (3认同)

R2D*_*2M2 21

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

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();
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);
Run Code Online (Sandbox Code Playgroud)

优点:

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

缺点:

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


小智 21

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

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()) {};
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,如果你需要调用需要一些反射的构造函数,但是这个文档记录很清楚,这个技巧不是!

这是TypeTokenJavaDoc.

  • 这个解决方案适用于一组有限的案例,就像@noah的反思一样.我今天尝试了所有这些...最后我将参数类的一个实例传递给参数化类(为了能够调用.newInstance())."仿制药"非常缺乏...新的Foo <Bar>(Bar.class); ...类Foo <T> {private final Class <T> mTFactory; Foo(Class <T> tClass){mTFactory = tClass; ...} T instance = tFactory.newInstance(); } (6认同)
  • `公开课Q26289147`?为什么呢 (2认同)

Ing*_*ngo 12

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

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
Run Code Online (Sandbox Code Playgroud)

  • 从技术上讲,你没有传递函数,你传递的是*函数对象*(也称为*函子*). (6认同)
  • 或者也许是为了克服捕获"异常"而使用"供应商<E>". (3认同)

Ser*_*nko 8

Java教程 - 泛型限制:

无法创建类型参数的实例

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

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}
Run Code Online (Sandbox Code Playgroud)

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

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}
Run Code Online (Sandbox Code Playgroud)

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

List<String> ls = new ArrayList<>();
append(ls, String.class);
Run Code Online (Sandbox Code Playgroud)

  • `cls.newInstance()` 已被贬值,取而代之的是 `cls.getDeclaredConstructor().newInstance()`。 (2认同)

Ira*_*Ira 7

当您在编译时使用 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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Mik*_*one 6

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:或者你可以使用这个构造函数(但它需要一个E实例):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}
Run Code Online (Sandbox Code Playgroud)


jb.*_*jb. 6

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

new SomeContainer<SomeType>(SomeType.class);
Run Code Online (Sandbox Code Playgroud)

您可以使用工厂方法:

<E> SomeContainer<E> createContainer(Class<E> class); 
Run Code Online (Sandbox Code Playgroud)

像:

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

}
Run Code Online (Sandbox Code Playgroud)


Nee*_*eep 6

Java不幸地不允许你想做什么.查看官方解决方法:

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

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}
Run Code Online (Sandbox Code Playgroud)

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

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}
Run Code Online (Sandbox Code Playgroud)

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

List<String> ls = new ArrayList<>();
append(ls, String.class);
Run Code Online (Sandbox Code Playgroud)

  • 我猜你投了反对票,因为你的答案与 Justin Rudd 的答案基本相同:/sf/answers/5267811/ (3认同)

小智 5

您可以使用:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)
Run Code Online (Sandbox Code Playgroud)

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

  • 以及如何在运行时获得泛型类型的确切类名? (15认同)

Se *_*ong 5

希望这不是太晚的帮助!!!

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

就我而言,我无法将参数传递给该createContents方法。我的解决方案是使用扩展,与下面的答案不同。

private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我无法传递参数的示例。

public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Ada*_*eld 0

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