在Java中创buildgenericstypes的实例?
是否有可能在Java中创build一个genericstypes的实例? 我在想,根据我所看到的答案是no
( 由于types擦除 ),但是如果有人能够看到我失踪的东西,我会感兴趣:
class SomeContainer<E> { E createContents() { return what??? } }
编辑:事实certificate, 超级types的令牌可以用来解决我的问题,但它需要很多基于reflection的代码,如下面的一些答案已经表明。
我会把这个开放一段时间,看看是否有人提出任何与伊恩·罗伯逊的Artima条款截然不同的东西 。
你是对的。 你不能做“新E”。 但是你可以改变它
private static class SomeContainer<E> { E createContents(Class<E> clazz) { return clazz.newInstance(); } }
这是一个痛苦。 但它的作品。 用工厂模式包装它使它更容易一点。
不知道如果这有帮助,但是当你inheritance(包括匿名)generics时,types信息可以通过reflection获得。 例如,
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 );
但是这是很多的工作,只适用于子类。 可以很方便。
你需要某种forms的抽象工厂来传递:
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(); } }
在Java 8中,您可以使用Supplier
function界面轻松实现:
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
是一个构造函数引用 。
如果您的构造函数使用参数,则可以使用lambdaexpression式:
SomeContainer<BigInteger> bigIntegerContainer = new SomeContainer<>(() -> new BigInteger(1));
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()); } }
如果你需要一个generics类中的types参数的新实例,然后让你的构造函数要求它的类…
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的超级types令牌(STT)方法更简单(也更less问题)。
- 比STT方法更有效率(早餐会吃掉你的手机)。
缺点:
- 无法将Class传递给默认的构造函数(这就是为什么Foo是最终的)。 如果你真的需要一个默认的构造函数,你总是可以添加一个setter方法,但是之后你必须记得给她打电话。
- 罗伯逊的反对意见…更多酒吧比黑羊(虽然再次指定types参数类将不会完全杀死你)。 与Robertson的说法相反,这并不违反DRY的原则,因为编译器会确保types的正确性。
- 不完全是certificate。 对于初学者…如果types参数类没有默认的构造函数,
newInstance()
将抛出一个摇摆。 尽pipe如此,这确实适用于所有已知的解决scheme。 - 缺乏STT方法的总体封装。 没有什么大不了的(考虑到STT的巨大的性能开销)。
你现在可以做到这一点,它不需要一堆reflection代码。
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()) {}; } }
当然,如果你需要调用需要一些reflection的构造函数,但这是非常有据可查的,这个技巧不是!
这是用于TypeToken的JavaDoc 。
考虑一个更加function化的方法:不要从无到有(这显然是一种代码味道)创build一个E,而是传递一个知道如何创build一个的函数,即
E createContents(Callable<E> makeone) { return makeone.call(); // most simple case clearly not that useful }
从Java教程 – 对generics的限制 :
无法创buildtypes参数的实例
你不能创build一个types参数的实例。 例如,下面的代码导致编译时错误:
public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); }
作为解决方法,您可以通过reflection来创buildtypes参数的对象:
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);
这是我想出的一个选项,它可能有助于:
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(); }
如果您不希望在实例化过程中input两次类名,如:
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(); } }
当你在编译时使用E时,你并不关心实际genericstypes“E”(要么使用reflection,要么使用genericstypes的基类),所以让子类提供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(); } }
我想我可以做到这一点,但相当失望:这是行不通的,但我认为它仍然值得分享。
也许有人可以纠正:
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行是[*]
。
唯一可行的解决scheme是@JustinRudd
你可以用下面的代码实现这个function:
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 ); } }
您可以使用:
Class.forName(String).getConstructor(arguments types).newInstance(arguments)
但是你需要提供确切的类名,包括包,例如。 java.io.FileInputStream
。 我用这个来创build一个mathexpression式parsing器。
正如你所说,由于types删除,你不能真正做到这一点。 你可以使用reflection进行sorting,但是它需要大量的代码和大量的error handling。
如果你的意思是new E()
那么这是不可能的。 我想补充一点,它并不总是正确的 – 你怎么知道E是否有公共的无参数构造函数? 但是你总是可以将创build委托给其他知道如何创build一个实例的Class<E>
– 它可以是Class<E>
或者你的自定义代码
interface Factory<E>{ E create(); } class IntegerFactory implements Factory<Integer>{ private static int i = 0; Integer create() { return i++; } }
return (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
@诺亚答案的一个不足之处。
变化的原因
a]如果您更改了订单,则使用多于1个genericstypes会更安全。
b]类genericstypes签名会不时发生变化,以免在运行时出现无法解释的exception。
健壮的代码
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); } } } }
或者使用一个class轮
一行代码
model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
有各种各样的库可以使用类似于罗伯逊文章所讨论的技术来解决你的问题。 下面是使用TypeTools来parsing由E表示的原始类的createContents
实现:
E createContents() throws Exception { return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance(); }
这假定getClass()parsing为SomeContainer的子类,否则将失败,因为E的实际参数化值在运行时如果未在子类中捕获,则会被擦除。
下面是createContents
的一个实现,它使用TypeTools来parsing由E
表示的原始类:
E createContents() throws Exception { return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance(); }
这种方法只适用于SomeContainer
子类,所以E
的实际值被捕获在types定义中:
class SomeStringContainer extends SomeContainer<String>
否则,E的值会在运行时被擦除,并且不可恢复。
你可以用一个类加载器和类名,最终得到一些参数。
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);
Java不幸地不允许你想要做什么。 请参阅http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#createObjects
你不能创build一个types参数的实例。 例如,下面的代码导致编译时错误:
public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); }
作为解决方法,您可以通过reflection来创buildtypes参数的对象:
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);