是否可以在Java中创建泛型类型的实例?我的想法是基于我所看到的答案是no(由于类型删除),但如果有人能看到我丢失的东西,我会感兴趣的:
1 2 3 4 5 6 7
| class SomeContainer<E>
{
E createContents()
{
return what???
}
} |
编辑:事实证明,超级类型的令牌可以用来解决我的问题,但是它需要很多基于反射的代码,正如下面的一些答案所指出的那样。
我会把这个打开一段时间,看看是否有人想出任何明显不同于伊恩罗伯逊的Artima文章。
你是对的。你不能做new E()。但是你可以把它改成
1 2 3 4 5
| private static class SomeContainer<E> {
E createContents(Class<E> clazz) {
return clazz.newInstance();
}
} |
这是一种痛苦。但它有效。用工厂的方式包装它会让它更容易接受。
不知道,如果这有帮助,但是当您对一个泛型类型进行子类(包括匿名)时,类型信息可以通过反射获得。例如。,
1 2 3 4 5 6 7 8 9 10 11
| public abstract class Foo<E> {
public E instance;
public Foo() throws Exception {
instance = ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
...
}
} |
因此,当您将foo子类化时,会得到一个bar的实例,例如,
1 2
| // notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar ); |
但是这是一个很大的工作,而且只适用于子类。不过也很方便。
在Java 8中,您可以使用EDCOX1 6功能接口来实现这一点:
1 2 3 4 5 6 7 8 9 10 11
| class SomeContainer<E> {
private Supplier<E> supplier;
SomeContainer(Supplier<E> supplier) {
this.supplier = supplier;
}
E createContents() {
return supplier.get();
}
} |
您可以这样构造这个类:
1
| SomeContainer<String> stringContainer = new SomeContainer<>(String::new); |
该行的语法String::new是一个构造函数引用。
如果构造函数接受参数,则可以改用lambda表达式:
1 2
| SomeContainer<BigInteger> bigIntegerContainer
= new SomeContainer<>(() -> new BigInteger(1)); |
你需要某种抽象的工厂来把责任转嫁给:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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();
}
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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());
}
} |
如果需要泛型类内类型参数的新实例,则使构造函数要求其类…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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();
...
}
} |
用途:
1 2
| Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class); |
赞成的意见:
- 比罗伯逊的超类型令牌(STT)方法简单得多(问题也更少)。
- 比STT方法更有效(它会在早餐时吃掉你的手机)。
欺骗:
- 无法将类传递给默认构造函数(这就是foo是final的原因)。如果您确实需要一个默认的构造函数,您可以始终添加一个setter方法,但之后必须记住给她一个调用。
- 罗伯逊反对…更多的条比一个黑绵羊(尽管再次指定类型参数类不会完全杀死您)。与罗伯逊的说法相反,这无论如何都不会违反dry原则,因为编译器将确保类型的正确性。
- 不完全是Foo证据。首先……如果类型参数类没有默认的构造函数,newInstance()将抛出一个摇摆器。不过,这确实适用于所有已知的解决方案。
- 缺乏STT方法的完全封装。不过没什么大不了的(考虑到STT的惊人的性能开销)。
您现在可以这样做,它不需要一堆反射代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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()) {};
}
} |
当然,如果您需要调用需要一些反射的构造函数,但这是非常好的文档记录,那么这个技巧就不是了!
这是用于typetoken的javadoc。
考虑一个更实用的方法:不要凭空创建一些e(这显然是一种代码味道),而是传递一个知道如何创建e的函数,即。
1 2 3
| E createContents(Callable<E> makeone) {
return makeone.call(); // most simple case clearly not that useful
} |
从Java教程-对泛型的限制:
无法创建类型参数的实例
无法创建类型参数的实例。例如,以下代码会导致编译时错误:
1 2 3 4
| public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
} |
作为解决方法,您可以通过反射创建类型参数的对象:
1 2 3 4
| public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
} |
您可以调用append方法,如下所示:
1 2
| List<String> ls = new ArrayList<>();
append(ls, String.class); |
如果不想在实例化期间键入两次类名,如:
1
| new SomeContainer<SomeType>(SomeType.class); |
您可以使用工厂方法:
1
| <E> SomeContainer<E> createContainer(Class<E> class); |
喜欢:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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();
}
} |
以下是我提出的一个选项,它可能有助于:
1 2 3 4 5 6 7 8 9 10 11
| 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的实例):
1 2 3 4
| @SuppressWarnings("unchecked")
public Container(E instance) {
this.clazz = (Class<E>) instance.getClass();
} |
JAVA不幸的是不允许你想做什么。参见官方解决方案:
You cannot create an instance of a type parameter. For example, the following code causes a compile-time error:
1 2 3 4
| public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
} |
As a workaround, you can create an object of a type parameter through reflection:
1 2 3 4
| public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
} |
You can invoke the append method as follows:
1 2
| List<String> ls = new ArrayList<>();
append(ls, String.class); |
在编译时使用e时,实际上并不关心实际的泛型类型"e"(使用反射或使用泛型类型的基类),因此让子类提供e的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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();
}
} |
你可以使用:
1
| Class.forName(String).getConstructor(arguments types).newInstance(arguments) |
但是您需要提供准确的类名,包括包,例如java.io.FileInputStream。我用这个创建了一个数学表达式解析器。
我以为我能做到,但很失望:它不起作用,但我认为它仍然值得分享。
也许有人可以纠正:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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()+"]");
}
} |
它产生:
1 2 3 4 5 6 7
| 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提供的解决方案
对诺亚回答的否定。
变更原因
如果在更改顺序的情况下使用多个泛型类型,则A]更安全。
B]类的泛型类型签名会不时更改,这样您就不会对运行时中无法解释的异常感到惊讶。
鲁棒代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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);
}
}
}
} |
或者用一个衬垫
单行代码
1
| model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance(); |
您可以通过以下代码片段来实现这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 );
}
} |
1
| return (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance(); |
正如你所说,你不能真的去做,因为类型删除。您可以使用反射来完成它,但是它需要大量的代码和大量的错误处理。
有各种各样的库可以使用类似于Robertson文章讨论的技术为您解析E。下面是createContents的一个实现,它使用类型工具解析由e表示的原始类:
1 2 3
| E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
} |
这假定getClass()解析为SomeContainer的子类,否则将失败,因为如果E的实际参数化值未捕获到子类中,那么它将在运行时被擦除。
如果你是说new E()那是不可能的。我还要补充一点,这并不总是正确的——您如何知道e是否具有public no args构造函数?但是,您总是可以将创建委托给其他知道如何创建实例的类-它可以是Class或您的自定义代码,如下面所示
1 2 3 4 5 6 7 8 9 10
| interface Factory<E>{
E create();
}
class IntegerFactory implements Factory<Integer>{
private static int i = 0;
Integer create() {
return i++;
}
} |
下面是createContents的一个实现,它使用typetools解析E表示的原始类:
1 2 3
| E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
} |
此方法仅在SomeContainer被子类化,以便在类型定义中捕获E的实际值时有效:
1
| class SomeStringContainer extends SomeContainer<String> |
否则,e的值在运行时被擦除,不可恢复。
希望这不会太晚帮不上忙!!!!
Java是类型安全的,只有对象是能够创建实例的。
在我的例子中,我不能将参数传递给createContents方法。我的解决方案是使用扩展而不是所有下面的答案。
1 2 3 4 5 6
| private static class SomeContainer<E extends Object> {
E e;
E createContents() throws Exception{
return (E) e.getClass().getDeclaredConstructor().newInstance();
}
} |
这是我的例子,我不能传递参数。
1 2 3 4 5 6 7
| public class SomeContainer<E extends Object> {
E object;
void resetObject throws Exception{
object = (E) object.getClass().getDeclaredConstructor().newInstance();
}
} |
如果用无对象类型扩展泛型类,则使用反射创建运行时错误。若要将泛型类型扩展到对象,请将此错误转换为编译时错误。
可以使用类加载器和类名,最后是一些参数。
1 2 3 4 5
| 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); |
这里有一个改进的解决方案,基于ParameterizedType.getActualTypeArguments,已经被@noah、@lars bohl和其他一些人提到。
实施中的第一个小改进。工厂不应返回实例,而应返回类型。一旦您使用Class.newInstance()返回实例,就减少了使用范围。因为只有没有参数构造函数可以这样调用。更好的方法是返回一个类型,并允许客户机选择他要调用的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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);
}
}
} |
下面是一个用法示例。@拉尔斯·波尔只展示了一种通过扩展获得身份化的通用性的签名方式。@noah只能通过使用{}创建一个实例。以下是演示这两种情况的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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);
}
} |
注意:当通过将类抽象为:public abstract class TypeReference创建实例时,可以强制TypeReference的客户机始终使用{}。我没有做,只是为了显示删除的测试用例。