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

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

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

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

我将把这个问题保留一段时间,看看是否有人提出了与Ian Robertson的Artima文章截然不同的观点。

566279 次浏览

正如你说的,你不能真正做到这一点,因为类型擦除。你可以使用反射来实现,但这需要大量的代码和错误处理。

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

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

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

这是我想到的一个选择,可能会有帮助:

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

EDIT:你也可以使用这个构造函数(但它需要一个E的实例):

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

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

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

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

你需要某种抽象工厂来把责任传递给:

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

如果你是说 # EYZ0 那就不可能了。我想补充一点,它并不总是正确的——你怎么知道E是否有公共的无参数构造函数? 但是你总是可以将创建委托给其他知道如何创建实例的类-它可以是Class<E>或您的自定义代码,如

interface Factory<E>{
E create();
}


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

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

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


}

你可以使用:

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

但是您需要提供确切的类名,包括包,例如。# EYZ0。我使用它来创建数学表达式解析器。

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


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

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

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

从# EYZ0:

不能创建类型参数的实例

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

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

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

也许有人可以纠正:

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

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

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方法更有效(STT方法会把你的手机当早餐吃)。

缺点:

  • 不能将Class传递给默认构造函数(这就是Foo是final的原因)。如果你确实需要一个默认构造函数,你总是可以添加一个setter方法,但你必须记得稍后给她一个调用。
  • 罗伯逊的反对……bar比害群之马多(尽管再一次指定类型参数类不会完全杀死你)。与Robertson的说法相反,这并不违反DRY原则,因为编译器将确保类型的正确性。
  • 不完全是Foo<L>proof。首先……如果type参数类没有默认构造函数,newInstance()将抛出抖动。这确实适用于所有已知的解。
  • 缺少STT方法的完全封装。不过这不是什么大问题(考虑到STT的性能开销)。

你可以用类加载器和类名,最终一些参数。

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

当你在编译时使用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();
}
}

现在就可以这样做,而且不需要一堆反射代码。

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

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

这是JavaDoc用于TypeToken

对诺亚的回答进行了改进。

改变的原因

一个)如果使用多个泛型类型则更安全,以防您更改顺序。

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

健壮的代码

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

考虑一种更函数化的方法:与其凭空创建一些E(这显然是一种代码味道),不如传递一个知道如何创建E的函数。

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

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

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

下面是一个改进的解决方案,基于ParameterizedType.getActualTypeArguments, @noah, @Lars Bohl和其他一些人已经提到过。

首先是实施上的小改进。Factory不应该返回实例,而是返回类型。一旦使用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>。我没有这样做,只是为了显示被擦除的测试用例。

希望这还不算太晚!!

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

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

你能做的就是——

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

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

    对象
  2. 那就在任何你想用的地方用吧

的例子,

1

# EYZ0

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

使用TypeToken<T>类:

public class MyClass<T> {
public T doSomething() {
return (T) new TypeToken<T>(){}.getRawType().newInstance();
}
}

注意,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)
}

我受到了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方法(或者不实现它,以防你不需要它)。

正如您所提到的,您不能从泛型中获得实例。在我看来,你必须改变设计,使用FACTORY METHOD设计模式。通过这种方式,你不需要你的类或方法是泛型:

class abstract SomeContainer{
Parent execute(){
return method1();
}
    

abstract Parent method1();
}




class Child1 extends Parent{
Parent method1(){
return new Parent();
}
}


class Child2 extends Parent{
Parent method1(){
return new Child2();
}
}