有可能在Java中创建泛型类型的实例吗?我在想,根据我所看到的,答案是否定的(由于类型擦除),但如果有人能看到我遗漏的东西,我会很感兴趣:
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
编辑:事实证明,超级类型令牌可以用来解决我的问题,但它需要大量基于反射的代码,如下面的一些答案所示。
我将把这个问题放一段时间,看看是否有人提出了与Ian Robertson的Artima文章截然不同的东西。
我不知道这是否有帮助,但当您子类化(包括匿名)泛型类型时,类型信息可以通过反射获得。例如,
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教程-泛型的限制:
不能创建类型参数的实例
不能创建类型参数的实例。例如,下面的代码会导致编译时错误:
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方法会把你的手机当早餐吃)。
缺点:
Can't pass Class to a default constructor (which is why Foo is final). If you really do need a default constructor you can always add a setter method but then you must remember to give her a call later.
Robertson's objection... More Bars than a black sheep (although specifying the type argument class one more time won't exactly kill you). And contrary to Robertson's claims this does not violate the DRY principal anyway because the compiler will ensure type correctness.
Not entirely Foo<L>proof. For starters... newInstance() will throw a wobbler if the type argument class does not have a default constructor. This does apply to all known solutions though anyway.
Lacks the total encapsulation of the STT approach. Not a big deal though (considering the outrageous performance overhead of STT).
对诺亚的回答进行了改进。
改变的原因
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();
下面是一个createContents的实现,它使用TypeTools(我编写的)来解析由E表示的原始类:
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
这种方法只适用于SomeContainer被子类化,所以E的实际值在类型定义中被捕获:
class SomeStringContainer extends SomeContainer<String>
否则E的值将在运行时被擦除,并且不可恢复。
不幸的是,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的客户端在创建实例时总是使用{},方法是让这个类成为抽象类:我没有这样做,只是为了显示被擦除的测试用例。
希望这还不算太晚!!
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();
}
}
如果使用非对象类型扩展泛型类,则使用反射会产生运行时错误。将泛型类型扩展为对象,将此错误转换为编译时错误。
注意,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)
}
没有默认构造函数
Objenesis