【问题标题】:Instantiating a generic class in Java [duplicate]在Java中实例化一个泛型类[重复]
【发布时间】:2010-11-08 14:15:44
【问题描述】:

我知道 Java 的泛型不如 .Net 的。

我有一个通用类Foo<T>,我真的需要使用无参数构造函数在Foo 中实例化一个T。如何解决 Java 的限制?

【问题讨论】:

  • 这一直困扰着我。不是 Java 专家,而是一位教授最近告诉我:你做不到。
  • 在 Java 中解决这个问题的通常方法是使用 Class 对象作为类型标记,然后使用它反射地实例化您需要的对象。
  • “反射实例化”是什么意思?
  • 如下 Alireza Fattahi 所链接;请参阅 Java 8 供应商stackoverflow.com/a/36315051/2648077 的使用

标签: java generics


【解决方案1】:

一种选择是传入Bar.class(或您感兴趣的任何类型 - 指定适当Class<T> 引用的任何方式)并将该值保留为字段:

public class Test {
    public static void main(String[] args) throws IllegalAccessException,
            InstantiationException {
        Generic<Bar> x = new Generic<>(Bar.class);
        Bar y = x.buildOne();
    }
}

public class Generic<T> {
    private Class<T> clazz;

    public Generic(Class<T> clazz) {
        this.clazz = clazz;
    }

    public T buildOne() throws InstantiationException, IllegalAccessException {
        return clazz.newInstance();
    }
}

public class Bar {
    public Bar() {
        System.out.println("Constructing");
    }
}

另一种选择是拥有一个“工厂”接口,并将工厂传递给泛型类的构造函数。这样更加灵活,而且您无需担心反射异常。

【讨论】:

  • 为什么不将new Foo() 作为参数传递?
  • @fastcodejava,因为你需要传递 Class 类型。 Foo 和 Class 是不同的类型。
  • 如果在上面的例子中 T 类型本身就是一个泛型类型怎么办?那么我们是否必须与原始类型妥协?
  • @AayushShrivastava:我不这么认为 - 但老实说,我已经有好几年没有定期编写 Java 了。
  • 谢谢。这正是我要找的!
【解决方案2】:

这是工厂实现,如Jon Skeet suggested

interface Factory<T> {
    T factory();
}

class Araba {
    //static inner class for Factory<T> implementation
    public static class ArabaFactory implements Factory<Araba> {
        public Araba factory() {
            return new Araba();
        }
    }
    public String toString() { return "Abubeee"; }
}

class Generic<T> {
    private T var;

    Generic(Factory<T> fact) {
        System.out.println("Constructor with Factory<T> parameter");
        var = fact.factory();
    }
    Generic(T var) {
        System.out.println("Constructor with T parameter");
        this.var = var;
    }
    T get() { return var; }
}

public class Main {
    public static void main(String[] string) {
        Generic<Araba> gen = new Generic<Araba>(new Araba.ArabaFactory());
        System.out.print(gen.get());
    }
}

输出:

Constructor with Factory<T> parameter
Abubeee

【讨论】:

  • 这一切都很好,而且……它确实有效。但是......如果我们只需要一个新的 Bar 实例(因为 &lt;T&gt;&lt;Bar&gt;),那么用Class&lt;Bar&gt; token = Bar.class;Bar newBar = token.newInstance(); 反射地创建一个 Bar 实例对我来说似乎就不那么冗长了。
  • @scottb:你可能没有一个可以用无参数构造函数创建的简单类。在这种情况下,工厂将是比反射类实例化更可靠的解决方案。
  • @codethulhu:同意。然而,原始问题的规定之一是他打算使用“无参数构造函数”。
【解决方案3】:

这是一种相当人为的方法,无需显式使用构造函数参数。你需要扩展一个参数化的抽象类。

public class Test {   
    public static void main(String [] args) throws Exception {
        Generic g = new Generic();
        g.initParameter();
    }
}

import java.lang.reflect.ParameterizedType;
public abstract class GenericAbstract<T extends Foo> {
    protected T parameter;

    @SuppressWarnings("unchecked")
    void initParameter() throws Exception, ClassNotFoundException, 
        InstantiationException {
        // Get the class name of this instance's type.
        ParameterizedType pt
            = (ParameterizedType) getClass().getGenericSuperclass();
        // You may need this split or not, use logging to check
        String parameterClassName
            = pt.getActualTypeArguments()[0].toString().split("\\s")[1];
        // Instantiate the Parameter and initialize it.
        parameter = (T) Class.forName(parameterClassName).newInstance();
    }
}

public class Generic extends GenericAbstract<Foo> {
}

public class Foo {
    public Foo() {
        System.out.println("Foo constructor...");
    }
}

【讨论】:

  • 好的,我明白了。您甚至可以更简单地将其编写为匿名类:“GenericAbstract g = new GenericAbstract() { }; g.initParameter();”并且 "" 不是必需的,只需 "" 就可以了。但这对我来说似乎还是没用,只是通过声明另一个类将 Foo.class 传递给对象的一种复杂方式
  • @newacct 实际上我认为它可以帮助我创建一个 BuffedArrayList 最终将帮助我避免令人讨厌的 toArray(new Something[0]),所以我可以轻松拥有T toArray()
  • @Glenn 它也适用于 ArrayList 但我必须像这样实例化:new ArrayList&lt;Foo&gt;(){} 不知道为什么...
【解决方案4】:

我真的需要在 Foo 中使用无参数实例化一个 T 构造函数

简单的答案是“你不能那样做”java 使用类型擦除来实现泛型,这会阻止你这样做。

如何解决 Java 的限制?

一种方法(可能还有其他方法)是将您将传递 T 的实例的对象传递给 Foo&lt;T&gt; 的构造函数。或者你可以有一个方法 setBar(T theInstanceofT); 来获取你的 T 而不是在它自己的类中实例化。

【讨论】:

    【解决方案5】:

    对于 Java 8 ....

    https://stackoverflow.com/a/36315051/2648077 帖子有一个很好的解决方案。

    这里使用Java 8 Supplier功能接口

    【讨论】:

    • 我打算发布这个作为答案。这是迄今为止最安全、最简单的方法!
    【解决方案6】:

    来自https://stackoverflow.com/a/2434094/848072。你需要一个 T 类的默认构造函数。

    import java.lang.reflect.ParameterizedType;
    
    class Foo<T> {
      
      public bar() {
        ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
        Class<T> type = (Class<T>) superClass.getActualTypeArguments()[0];
        try {
          T t = (T) type.getDeclaredConstructor().newInstance();
          //Do whatever with t
        } catch (Exception e) {
          // Oops, no default constructor
          throw new RuntimeException(e);
        } 
      }
    }
    

    【讨论】:

    • 完美运行!谢谢!
    【解决方案7】:

    Java 中的泛型通常比 C# 中的更强大。

    如果您想构造一个对象但不使用构造函数/静态方法,请使用抽象工厂。您应该能够在任何基本设计模式书籍、OOP 简介或所有互联网上找到有关抽象工厂模式的详细信息和教程。在这里不值得重复代码,除了提到 Java 的闭包语法很烂。

    IIRC,C# 有一个特殊情况,用于指定泛型类型具有无参数构造函数。根据定义,这种不规则性预设了客户端代码想要使用这种特殊的构造形式并鼓励可变性。

    为此使用反射是错误的。 Java 中的泛型是一种编译时的静态类型特性。尝试在运行时使用它们清楚地表明出现了问题。反射会导致冗长的代码、运行时失败、未经检查的依赖关系和安全漏洞。 (Class.forName 特别邪恶。)

    【讨论】:

    • "Java 中的泛型通常比 C# 中的更强大" wut.他们甚至没有被具体化。
    • @WChargin:是的,这就是为什么 Java 泛型保证类型安全要求您的代码编译时没有错误或任何警告。如果您的代码没有生成警告,则可以保证您的代码是类型安全的,没有任何运行时类型检查的开销。 Java 泛型是否“更好”取决于您是半满还是半空的人。
    【解决方案8】:

    我可以在 JUnit 测试设置中执行此操作。

    我想测试一个 Hibernate 外观,所以我一直在寻找一种通用的方法来完成它。请注意,外观还实现了一个通用接口。这里 T 是数据库类,U 是主键。 Ifacade&lt;T,U&gt; 是一个门面,用主键 U 访问数据库对象 T。

    public abstract class GenericJPAController<T, U, C extends IFacade<T,U>>
    
    {
        protected static EntityManagerFactory emf;
    
        /* The properties definition is straightforward*/
        protected T testObject;
        protected C facadeManager;
    
        @BeforeClass
        public static void setUpClass() {
    
    
            try {
                emf = Persistence.createEntityManagerFactory("my entity manager factory");
    
            } catch (Throwable ex) {
                System.err.println("Failed to create sessionFactory object." + ex);
                throw new ExceptionInInitializerError(ex);
            }
    
        }
    
        @AfterClass
        public static void tearDownClass() {
        }
    
        @Before
        public void setUp() {
        /* Get the class name*/
            String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2].getTypeName();
    
            /* Create the instance */
            try {
                facadeManager = (C) Class.forName(className).newInstance();
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
                Logger.getLogger(GenericJPAController.class.getName()).log(Level.SEVERE, null, ex);
            }
            createTestObject();
        }
    
        @After
        public void tearDown() {
        }
    
        /**
         * Test of testFindTEntities_0args method, of class
         * GenericJPAController<T, U, C extends IFacade<T,U>>.
         * @throws java.lang.ClassNotFoundException
         * @throws java.lang.NoSuchMethodException
         * @throws java.lang.InstantiationException
         * @throws java.lang.IllegalAccessException
         */
        @Test
        public void  testFindTEntities_0args() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException {
    
            /* Example of instance usage. Even intellisense (NetBeans) works here!*/
            try {
                List<T> lista = (List<T>) facadeManager.findAllEntities();
                lista.stream().forEach((ct) -> {
                    System.out.println("Find all: " + stringReport());
                });
            } catch (Throwable ex) {
                System.err.println("Failed to access object." + ex);
                throw new ExceptionInInitializerError(ex);
            }
        }
    
    
        /**
         *
         * @return
         */
        public abstract String stringReport();
    
        protected abstract T createTestObject();
        protected abstract T editTestObject();
        protected abstract U getTextObjectIndex();
    }
    

    【讨论】:

      【解决方案9】:

      使用Constructor.newInstance 方法。自 Java 9 起,Class.newInstance 方法已被弃用,以增强编译器对实例化异常的识别。

      public class Foo<T> {   
          public Foo()
          {
              Class<T> newT = null; 
              instantiateNew(newT);
          }
      
          T instantiateNew(Class<?> clsT)
          {
              T newT;
              try {
                  newT = (T) clsT.getDeclaredConstructor().newInstance();
              } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                  | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
                  return null;
              }
              return newT;
          }
      }
      

      【讨论】:

      • 这将抛出一个 NEP,因为 newT 为空
      【解决方案10】:

      对我有用的快速解决方案。 我看到已经有一个答案,这甚至可能不是最好的方法。另外,对于我的解决方案,您需要Gson

      但是,我遇到了需要创建java.lang.reflect.Type 类型的泛型类的实例的情况。

      以下代码将使用空实例变量创建您想要的类的实例。

      T object = new Gson().fromJson("{}", myKnownType);
      

      myKnownType 事先已知并通过TypeToken.getType() 获得。

      您现在可以在此对象上设置适当的属性。同样,这可能不是执行此操作的最佳方法,但如果您需要,它可以作为一种快速解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-18
        • 1970-01-01
        • 1970-01-01
        • 2015-03-06
        相关资源
        最近更新 更多