[英]Create instance of generic type in Java?
是否可以在 Java 中创建泛型实例? 根据我所看到的,我在想答案是no
的(由于类型擦除),但如果有人能看到我遗漏的东西,我会很感兴趣:
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
编辑:原来Super Type Tokens可以用来解决我的问题,但它需要大量基于反射的代码,正如下面的一些答案所表明的那样。
我会把它打开一会儿,看看是否有人想出与 Ian Robertson 的Artima 文章截然不同的东西。
你是对的。 你不能做new E()
。 但是你可以把它改成
private static class SomeContainer<E> {
E createContents(Class<E> clazz) {
return clazz.newInstance();
}
}
这是一种痛苦。 但它有效。 将它包装在工厂模式中使其更容易忍受。
我不知道这是否有帮助,但是当您子类化(包括匿名)泛型类型时,类型信息可通过反射获得。 例如,
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 );
但这需要大量工作,并且仅适用于子类。 不过可以很方便。
在 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));
你需要某种抽象工厂来推卸责任:
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();
}
}
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());
}
}
如果你需要一个泛型类中类型参数的新实例,那么让你的构造函数要求它的类......
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);
优点:
缺点:
Foo<L>
证明。 对于初学者......如果类型参数类没有默认构造函数, newInstance()
将抛出一个 wobbler 。 无论如何,这确实适用于所有已知的解决方案。您现在可以执行此操作,并且不需要一堆反射代码。
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()) {};
}
}
当然,如果您需要调用需要一些反射的构造函数,但有很好的文档记录,这个技巧不是!
考虑一种更实用的方法:与其从无到有创建一些 E(这显然是一种代码异味),不如传递一个知道如何创建 E 的函数,即
E createContents(Callable<E> makeone) {
return makeone.call(); // most simple case clearly not that useful
}
您不能创建类型参数的实例。 例如,以下代码会导致编译时错误:
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);
当您在编译时使用 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();
}
}
这是我想出的一个选项,它可能会有所帮助:
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();
}
如果您不想在实例化过程中两次键入类名,例如:
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();
}
}
不幸的是,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);
您可以使用:
Class.forName(String).getConstructor(arguments types).newInstance(arguments)
但是您需要提供确切的类名,包括包,例如。 java.io.FileInputStream
。 我用它来创建一个数学表达式解析器。
希望这不是太晚的帮助!!!
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();
}
}
如果使用无对象类型扩展泛型类,则使用反射会创建运行时错误。 要将泛型类型扩展为对象,请将此错误转换为编译时错误。
使用TypeToken<T>
类:
public class MyClass<T> {
public T doSomething() {
return (T) new TypeToken<T>(){}.getRawType().newInstance();
}
}
我以为我可以这样做,但很失望:它不起作用,但我认为它仍然值得分享。
也许有人可以纠正:
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
@Noah 答案的改进。
造成改变的原因
a]如果您更改顺序,则使用超过 1 个泛型类型会更安全。
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();
你能做的是——
首先声明该泛型类的变量
2.然后创建它的构造函数并实例化该对象
然后在任何你想使用的地方使用它
例子-
1
private Class<E> entity;
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(entity);
您可以使用以下代码段实现此目的:
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 );
}
}
有各种库可以使用类似于 Robertson 文章中讨论的技术为您解析E
下面是createContents
的一个实现,它使用TypeTools来解析由 E 表示的原始类:
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
这假设 getClass() 解析为 SomeContainer 的子类,否则将失败,因为如果 E 的实际参数化值未在子类中捕获,则在运行时将被删除。
下面是createContents
的一个实现,它使用TypeTools来解析由E
表示的原始类:
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
这种方法仅在SomeContainer
是子类化的情况下才有效,因此E
的实际值在类型定义中被捕获:
class SomeStringContainer extends SomeContainer<String>
否则 E 的值在运行时被擦除并且不可恢复。
return (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
这是一个改进的解决方案,基于ParameterizedType.getActualTypeArguments
,@noah、@Lars Bohl 和其他一些人已经提到过。
实施中的第一个小改进。 工厂不应该返回实例,而是一个类型。 一旦您使用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>
。 我没有这样做,只是为了显示擦除的测试用例。
请注意,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
方法(或者不实现它,以防你不需要它。
正如您提到的,您无法从 generics 获取实例。IMO,您必须更改设计并使用 FACTORY METHOD 设计模式。 通过这种方式,您不需要 class 或方法为 generics:
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();
}
}
正如您所说,由于类型擦除,您无法真正做到这一点。 您可以使用反射来完成它,但它需要大量代码和大量错误处理。
如果你的意思是new E()
那么这是不可能的。 我要补充一点,它并不总是正确的——你怎么知道 E 是否有公共无参数构造函数? 但是您始终可以将创建委托给其他知道如何创建实例的Class<E>
- 它可以是Class<E>
或您的自定义代码,如下所示
interface Factory<E>{
E create();
}
class IntegerFactory implements Factory<Integer>{
private static int i = 0;
Integer create() {
return i++;
}
}
你可以用一个类加载器和类名,最后还有一些参数。
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);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.