在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个回答

7
抱歉,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);

请问您为什么要在点踩的时候告诉我原因呢?我不明白为什么官方提供的解决方法不好。谢谢。 - Neepsnikeep
3
我猜你会收到负面评价,因为你的答案本质上与Justin Rudd的答案相同:https://dev59.com/qnVD5IYBdhLWcg3wI3-L#75254。 - Torsten

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

是的,即使没有泛型,这也可以正常工作——使用泛型时,该容器的实例化变得有点冗余(您必须两次指定“E”是什么)。 - David Citron
好吧,这就是使用Java和泛型的结果...它们并不美观,而且有严重的限制... - Mike Stone

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

}

5

您可以使用:

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

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


15
在运行时如何获取泛型类型的准确类名? - David Citron
你必须使用该类的实例来保存它。尽管不太方便,但这是可行的。如果你的泛型有一个类型为E(或T或其他)的成员,获取它的二进制名称就是foo.getClass().getName()。那个实例从哪里来的呢?目前我正在我现在工作的项目中将其传递给构造函数。 - Mark Storer

5
希望现在来帮忙不算太晚了!!!
Java是类型安全的,这意味着只有对象才能创建实例。
在我的情况下,我无法向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();
    }
}

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


4
使用 TypeToken<T> 类:
public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

1
如果您使用Guava而不是GSON,代码会有一点不同:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance(); - Jacob van Lingen

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 提供的。


3

@Noah的回答有所改进。

改动原因

a] 如果使用了多个通用类型并且更改了顺序,则更安全。

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

3
你可以这样做:
  1. 首先声明泛型类的变量
  2. 接着创建构造函数并实例化该对象
  3. 然后在需要使用的地方使用它
示例代码如下:

private Class<E> entity;

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



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

获取实体变量 e = getEntity(entity);


1
返回entity.newInstance();会触发警告:“自版本9以来,类型Class<E>的方法newInstance()已被弃用”。 - Daniel Methner

1
这是一个使用 TypeTools(由我编写)来解析 E 表示的原始类的 createContents 实现:
E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

这种方法只适用于SomeContainer被子类化,以便将E的实际值捕获在类型定义中:
class SomeStringContainer extends SomeContainer<String>

否则,E的值将在运行时被清除,且无法恢复。

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