在Java中创建泛型类型的实例?

在Java中创建泛型类型的实例?

Create instance of generic type in Java?

是否可以在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的客户机始终使用{}。我没有做,只是为了显示删除的测试用例。


推荐阅读

    linux创建命令简写?

    linux创建命令简写?,系统,数据,命令,文件,环境,档案,位置,文件夹,目录,终端,L

    linux命令创建pkg?

    linux命令创建pkg?,名称,文件,命令,系统,首次,数据,位置,不了,时间,名字,linu

    linux创建硬链接命令?

    linux创建硬链接命令?,数据,系统,链接,地方,信息,文件,概念,时间,位置,工作,L

    linux系统删除的命令?

    linux系统删除的命令?,软件,系统,名称,工具,不了,命令,文件夹,电脑,通用,信

    linux创建系列命令?

    linux创建系列命令?,名字,命令,文件,文件夹,代码,名称,电脑,地址,系统,密码,l

    linux命令实例练习?

    linux命令实例练习?,工作,系统,设备,代码,命令,信息,基础,网络,目录,文本,lin

    linux删除命令文件夹?

    linux删除命令文件夹?,系统,数据,通用,文件夹,命令,文件,环境,百度,不了,名

    linux删除本行命令?

    linux删除本行命令?,系统,本行,档案,命令,资料,商业,文件,终端,目录,文件名,L

    linux命令创建用户组?

    linux命令创建用户组?,系统,代码,密码,用户组,用户,命令,信息,名称,新增,管

    linuxln命令实例?

    linuxln命令实例?,位置,数据,链接,地方,信息,文件,系统,概念,名字,盘中,ln命

    linux删除第一行命令?

    linux删除第一行命令?,单位,系统,命令,标的,不了,数字,连续,名称,档案,文件,m

    linux删除本行命令?

    linux删除本行命令?,系统,本行,档案,命令,资料,商业,文件,终端,目录,文件名,L

    linux删除命令文件夹?

    linux删除命令文件夹?,系统,数据,通用,文件夹,命令,文件,环境,百度,不了,名

    linux命令创建用户组?

    linux命令创建用户组?,系统,代码,密码,用户组,用户,命令,信息,名称,新增,管

    linux创建主机名命令?

    linux创建主机名命令?,工作,地址,系统,信息,名称,命令,目录,发行,查询系统,

    linuxln命令实例?

    linuxln命令实例?,位置,数据,链接,地方,信息,文件,系统,概念,名字,盘中,ln命

    linux控制台创建命令?

    linux控制台创建命令?,工作,地址,系统,命令,信息,目录,管理,名字,文件,控制

    linux删除第一行命令?

    linux删除第一行命令?,单位,系统,命令,标的,不了,数字,连续,名称,档案,文件,m

    linux里删除区间命令?

    linux里删除区间命令?,系统,命令,情况,档案,不了,名称,目录,文件,文件夹,分

    linux基本命令实例?

    linux基本命令实例?,工作,地址,系统,信息,命令,标准,目录,基础,简介,功能,Lin