获取静态初始化块以在不加载类的情况下在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
块之前。 如果您不希望QuestionFactory
对QuestionFactory
实现有任何了解,则必须将它们列在由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(...)
加载每个文件。