获取静态初始化块以在不加载类的情况下在java中运行

我有几个课程,如下所示

public class TrueFalseQuestion implements Question{ static{ QuestionFactory.registerType("TrueFalse", "Question"); } public TrueFalseQuestion(){} } 

 public class QuestionFactory { static final HashMap map = new HashMap(); public static void registerType(String questionName, String ques ) { map.put(questionName, ques); } } public class FactoryTester { public static void main(String[] args) { System.out.println(QuestionFactory.map.size()); // This prints 0. I want it to print 1 } } 

如何更改TrueFalseQuestion类,以便始终运行静态方法,以便在运行main方法时得到1而不是0? 我不希望主方法有任何改变。

我实际上是在尝试实现子类向工厂注册的工厂模式,但我已经简化了这个问题的代码。

要在工厂中注册TrueFalseQuestion类,需要调用其静态初始化程序。 要执行TrueFalseQuestion类的静态初始化程序,需要引用该类,或者需要在调用QuestionFactory.map.size()之前通过reflection加载该类。 如果要保持main方法不变,则必须引用它或通过QuestionFactory静态初始化程序中的reflection加载它。 我不认为这是一个好主意,但我只会回答你的问题:)如果你不介意QuestionFactory知道实现Question所有类来构造它们,你可以直接引用它们或通过它们加载它们reflection。 就像是:

 public class QuestionFactory { static final HashMap map = new HashMap(); static { this.getClassLoader().loadClass("TrueFalseQuestion"); this.getClassLoader().loadClass("AnotherTypeOfQuestion"); // etc. } public static void registerType(String questionName, String ques ) { map.put(questionName, ques); } } 

确保map的声明和构造在static块之前。 如果您不希望QuestionFactoryQuestionFactory实现有任何了解,则必须将它们列在由QuestionFactory加载的配置文件中。 我能想到的唯一其他(可能是疯狂的)方法是查看实现Question :)的类的整个类路径。如果所有实现Question类都需要属于同一个包,那么这可能会更好 -注意:我不赞同这个解决方案;)

我不认为在QuestionFactory静态初始化程序中执行任何操作的原因是因为像TrueFalseQuestion这样的类有自己的静态初始化程序,它调用了QuestionFactory ,在那时它是一个不完整构造的对象,这只是一个问题。 有一个配置文件只是列出你想让QuestionFactory知道如何构造的类,然后在它的构造函数中注册它们是一个很好的解决方案,但它意味着改变你的main方法。

你可以打电话:

 Class.forName("yourpackage.TrueFalseQuestion"); 

这将加载类而不实际触摸它,并将执行静态初始化程序块。

如果从未加载类,则无法执行类的静态初始化程序。

所以你要么加载所有正确的类(这将很难,因为你在编译时都不知道它们)或者摆脱了对静态初始化程序的要求。

后者的一种方法是使用ServiceLoader

使用ServiceLoader您只需将文件放在META-INF/services/package.Question并列出所有实现。 您可以拥有多个此类文件,每个.jar文件一个。 这样,您可以轻松地发送与主程序分开的其他Question实现。

QuestionFactory您可以简单地使用ServiceLodaer.load(Question.class)来获取ServiceLoader ,它实现了Iterable并且可以像这样使用:

 for (Question q : ServiceLoader.load(Question.class)) { System.out.println(q); } 

为了运行静态初始化程序,需要加载类。 为此,要么“主”类必须(直接或间接)依赖于类,要么必须直接或间接地使它们动态加载; 例如,使用Class.forName(...)

我认为您正在尝试避免源代码中嵌入的依赖项。 因此静态依赖是不可接受的,并且使用硬编码类名调用Class.forName(...)也是不可接受的。

这留下了两个选择:

  • 编写一些杂乱的代码来迭代某些包中的资源名称,然后使用Class.forName(...)来加载那些看起来像您的类的资源。 如果您有一个复杂的类路径,这种方法很棘手,如果您的有效类路径包含带有远程URL的URLClassLoader(例如),那么这是不可能的。

  • 创建一个文件(例如一个类加载器资源),其中包含要加载的类名列表,并编写一些简单的代码来读取文件并使用Class.forName(...)加载每个文件。