首页 文章

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

提问于
浏览 1819 次
513

是否可以在Java中创建泛型类型的实例?我_167905已经看到答案是 no (由于类型擦除),但我错过了'd be interested if anyone can see something I':

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

编辑:事实证明Super Type Tokens可用于解决我的问题,但它需要大量基于反射的代码,如下面的一些答案所示 .

我是'll leave this open for a little while to see if anyone comes up with anything dramatically different than Ian Robertson' s Artima Article .

23 回答

  • 2

    这是我提出的一个选项,它可能会有所帮助:

    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();
    }
    
  • 118

    当您在编译时使用E时,您并不真正关心实际的泛型类型“E”(要么使用反射,要么使用泛型类型的基类),所以让子类提供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();
        }
    }
    
  • 6

    来自Java Tutorial - Restrictions on Generics

    Cannot Create Instances of Type Parameters

    您无法创建类型参数的实例 . 例如,以下代码导致编译时错误:

    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);
    
  • 82

    如你所说,由于类型擦除,你无法真正做到这一点 . 您可以使用反射进行排序,但它需要大量代码和大量错误处理 .

  • 8

    你现在可以这样做,它不需要一堆反射代码 .

    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()) {};
        }
    }
    

    当然,如果你需要调用需要反思的构造函数,但是这个文档记录很清楚,这个技巧不是!

    这是JavaDoc for TypeToken .

  • 0

    您可以使用以下代码段实现此目的:

    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 );
       }
    }
    
  • 19
    return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    
  • -1

    诺亚的回答是一个改进 .

    Reason for Change

    a] 如果您更改了订单,则使用超过1种泛型类型会更安全 .

    b] 类通用类型签名会不时更改,以便您不会对运行时中无法解释的异常感到惊讶 .

    Robust Code

    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);
                }
            }
        }
    }
    

    或使用一个衬垫

    One Line Code

    model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
    
  • 3

    如果你的意思是 new E() 那么这是不可能的 . 我想补充一点,这并不总是正确的 - 你怎么知道E是否有公共的无参数构造函数?但是您总是可以将创建委托给知道如何创建实例的其他类 - 它可以是 Class<E> 或您的自定义代码

    interface Factory<E>{
        E create();
    }    
    
    class IntegerFactory implements Factory<Integer>{    
      private static int i = 0; 
      Integer create() {        
        return i++;    
      }
    }
    
  • 2

    您可以使用类加载器和类名,最终使用一些参数 .

    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);
    
  • 18

    Java不幸地不允许你想做什么 . 见http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#createObjects

    您无法创建类型参数的实例 . 例如,以下代码导致编译时错误:public static <E> void append(List <E> list){
    E elem = new E(); //编译时错误
    list.add(ELEM);
    }
    作为一种变通方法,您可以通过反射创建一个类型参数的对象:public static <E> void append(List <E> list,Class <E> cls)throws Exception {
    E elem = cls.newInstance(); // 好
    list.add(ELEM);
    }
    您可以按如下方式调用append方法:List <String> ls = new ArrayList <>();
    append(ls,String.class);

  • 22
    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());
        }
    
    }
    
  • 298

    如果你需要一个泛型类中的类型参数的新实例,那么让你的构造函数需要它的类......

    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方法更有效率(早餐会吃你的手机) .

    缺点:

    • 无法将Class传递给默认构造函数(这就是为什么Foo是最终的) . 如果你确实需要一个默认的构造函数,你总是可以添加一个setter方法,但是你必须记得稍后给她打个电话 .

    • Robertson 's objection... More Bars than a black sheep (although specifying the type argument class one more time won' t完全杀了你) . 与Robertson的说法相反,这并不违反DRY主体,因为编译器将确保类型正确性 .

    • 不完全 Foo<L> 证明 . 对于初学者...如果类型参数类没有默认构造函数, newInstance() 将抛出一个摇摆器 . 尽管如此,这确实适用于所有已知的解决方案 .

    • 缺乏STT方法的完全封装 . 虽然没什么大不了的(考虑到STT的惊人性能开销) .

  • 85

    你是对的 . 你做不到 new E() . 但你可以改成它

    private static class SomeContainer<E> {
        E createContents(Class<E> clazz) {
            return clazz.newInstance();
        }
    }
    

    这是一种痛苦 . 但它的确有效 . 将其包装在工厂模式中使其更容易忍受 .

  • 0

    您可以使用:

    Class.forName(String).getConstructor(arguments types).newInstance(arguments)
    

    但是你需要提供确切的类名,包括包,例如 . java.io.FileInputStream . 我用它来创建一个数学表达式解析器 .

  • 0

    我以为我能做到这一点,但很失望:它不起作用,但我认为值得分享 .

    也许有人可以纠正:

    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

  • 0

    你需要某种抽象工厂来将这种抽象转移到:

    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();
        }
    }
    
  • 3

    在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::newconstructor reference .

    如果构造函数接受参数,则可以使用lambda表达式:

    SomeContainer<BigInteger> bigIntegerContainer
        = new SomeContainer<>(() -> new BigInteger(1));
    
  • 0

    这是 createContents 的一个实现,它使用TypeTools来解析由 E 表示的原始类:

    E createContents() throws Exception {
      return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
    }
    

    此方法仅在 SomeContainer 被子类化时才有效,因此在类型定义中捕获 E 的实际值:

    class SomeStringContainer extends SomeContainer<String>
    

    否则,E的值在运行时被擦除,不可恢复 .

  • 6

    如果您不希望在实例化期间两次键入类名,如:

    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();
        }
    
    }
    
  • 12

    Dunno如果这有帮助,但是当您子类化(包括匿名)泛型类型时,类型信息可通过反射获得 . 例如 . ,

    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 );
    

    但这是很多工作,只适用于子类 . 虽然可以派上用场 .

  • 4

    想想一个更实用的方法:不是从无到有创造一些E(这显然是代码气味),而是传递一个知道如何创建一个的函数,即

    E createContents(Callable<E> makeone) {
         return makeone.call(); // most simple case clearly not that useful
    }
    
  • 0

    有各种各样的库可以使用类似于Robertson文章讨论的技术为您解决 E . 这是 createContents 的实现,它使用TypeTools来解析由E表示的原始类:

    E createContents() throws Exception {
      return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
    }
    

    这假设getClass()解析为SomeContainer的子类,否则将失败,因为如果未在子类中捕获,则E的实际参数化值将在运行时被擦除 .

相关问题