是否可以在Java中创建泛型类型的实例?我正在考虑基于我所看到的答案是no
(由于类型擦除),但如果有人能看到我缺少的东西,我会感兴趣:
class SomeContainer{ E createContents() { return what??? } }
编辑:事实证明,超级类型标记可用于解决我的问题,但它需要大量基于反射的代码,如下面的一些答案所示.
我会把这个开放一段时间,看看是否有人提出了与Ian Robertson的Artima文章截然不同的任何东西.
你是对的.你做不到new E()
.但你可以改成它
private static class SomeContainer{ E createContents(Class clazz) { return clazz.newInstance(); } }
这是一种痛苦.但它的确有效.将其包装在工厂模式中使其更容易忍受.
Dunno如果这有帮助,但是当您子类化(包括匿名)泛型类型时,类型信息可通过反射获得.例如,
public abstract class Foo{ 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() {}.instance instanceof Bar );
但这是很多工作,只适用于子类.虽然可以派上用场.
在Java 8中,您可以使用Supplier
功能界面轻松实现此目的:
class SomeContainer{ private Supplier supplier; SomeContainer(Supplier supplier) { this.supplier = supplier; } E createContents() { return supplier.get(); } }
你可以这样构造这个类:
SomeContainerstringContainer = new SomeContainer<>(String::new);
该String::new
行的语法是构造函数引用.
如果构造函数接受参数,则可以使用lambda表达式:
SomeContainerbigIntegerContainer = new SomeContainer<>(() -> new BigInteger(1));
你需要某种抽象的工厂来传递:
interface Factory{ E create(); } class SomeContainer { private final Factory factory; SomeContainer(Factory factory) { this.factory = factory; } E createContents() { return factory.create(); } }
package org.foo.com; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * Basically the same answer as noah's. */ public class Home{ @SuppressWarnings ("unchecked") public Class getTypeParameterClass() { Type type = getClass().getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) type; return (Class ) paramType.getActualTypeArguments()[0]; } private static class StringHome extends Home { } private static class StringBuilderHome extends Home { } private static class StringBufferHome extends Home { } /** * 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()); } }
如果在泛型类中需要一个类型参数的新实例,那么让构造函数需要它的类......
public final class Foo{ private Class typeArgumentClass; public Foo(Class typeArgumentClass) { this.typeArgumentClass = typeArgumentClass; } public void doSomethingThatRequiresNewT() throws Exception { T myNewT = typeArgumentClass.newInstance(); ... } }
用法:
FoobarFoo = new Foo (Bar.class); Foo etcFoo = new Foo (Etc.class);
优点:
比Robertson的超级类型令牌(STT)方法简单得多(并且问题少).
比STT方法(早餐会吃你的手机)更有效率.
缺点:
无法将Class传递给默认构造函数(这就是为什么Foo是最终的).如果你真的需要一个默认的构造函数,你总是可以添加一个setter方法,但是你必须记得稍后给她打个电话.
Robertson的反对意见......比黑羊更多的酒吧(虽然再次指定类型参数类不会完全杀死你).与Robertson的说法相反,这并不违反DRY主体,因为编译器将确保类型正确性.
不完全Foo
证明.对于初学者... newInstance()
如果类型参数类没有默认构造函数,则会抛出一个摇摆器.尽管如此,这确实适用于所有已知的解决方案.
缺乏STT方法的完全封装.虽然没什么大不了的(考虑到STT的惊人性能开销).
你现在可以这样做,它不需要一堆反射代码.
import com.google.common.reflect.TypeToken; public class Q26289147 { public static void main(final String[] args) throws IllegalAccessException, InstantiationException { final StrawManParameterizedClasssmpc = new StrawManParameterizedClass () {}; final String string = (String) smpc.type.getRawType().newInstance(); System.out.format("string = \"%s\"",string); } static abstract class StrawManParameterizedClass { final TypeToken type = new TypeToken (getClass()) {}; } }
当然,如果你需要调用需要一些反射的构造函数,但是这个文档记录很清楚,这个技巧不是!
这是TypeToken的JavaDoc.
想想一个更实用的方法:不是从无到有创造一些E(这显然是代码气味),而是传递一个知道如何创建一个的函数,即
E createContents(Callablemakeone) { return makeone.call(); // most simple case clearly not that useful }
从Java教程 - 泛型限制:
无法创建类型参数的实例
您无法创建类型参数的实例.例如,以下代码导致编译时错误:
public staticvoid append(List list) { E elem = new E(); // compile-time error list.add(elem); }
作为解决方法,您可以通过反射创建类型参数的对象:
public staticvoid append(List list, Class cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem); }
您可以按如下方式调用append方法:
Listls = new ArrayList<>(); append(ls, String.class);
这是我提出的一个选项,它可能会有所帮助:
public static class Container{ private Class clazz; public Container(Class clazz) { this.clazz = clazz; } public E createContents() throws Exception { return clazz.newInstance(); } }
编辑:或者你可以使用这个构造函数(但它需要一个E实例):
@SuppressWarnings("unchecked") public Container(E instance) { this.clazz = (Class) instance.getClass(); }
如果你不希望在实例化过程中两次输入类名,如:
new SomeContainer(SomeType.class);
您可以使用工厂方法:
SomeContainer createContainer(Class class);
像:
public class Container{ public static Container create(Class c) { return new Container (c); } Class c; public Container(Class c) { super(); this.c = c; } public E createInstance() throws InstantiationException, IllegalAccessException { return c.newInstance(); } }
Java不幸地不允许你想做什么.查看官方解决方法:
您无法创建类型参数的实例.例如,以下代码导致编译时错误:
public staticvoid append(List list) { E elem = new E(); // compile-time error list.add(elem); }
作为解决方法,您可以通过反射创建类型参数的对象:
public staticvoid append(List list, Class cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem); }
您可以按如下方式调用append方法:
Listls = new ArrayList<>(); append(ls, String.class);