9

我有几个课程,如此处所示

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

...

public class QuestionFactory {

 static final HashMap<String, String > map =  new HashMap<String,String>();

 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?我不希望 main 方法有任何变化。

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

4

4 回答 4

6

您可以致电:

Class.forName("yourpackage.TrueFalseQuestion");

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

于 2010-04-06T05:50:26.237 回答
5

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

public class QuestionFactory {

 static final HashMap<String, String > map =  new HashMap<String,String>();

 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了解 的实现Question,则必须将它们列在由QuestionFactory. 我能想到的唯一其他(可能是疯狂的)方法是查看整个类路径以查找实现的类Question:) 如果所有实现的类Question都必须属于同一个包,那可能会更好——注意:我不支持这个解决方案;)

我不认为在QuestionFactory静态初始化程序中做任何这些的原因是因为类TrueFalseQuestion有自己的调用 into 的静态初始化程序QuestionFactory,此时它是一个不完全构造的对象,这只是在自找麻烦。拥有一个简单地列出您想知道如何构造的类的配置文件QuestionFactory,然后在其构造函数中注册它们是一个很好的解决方案,但这意味着更改您的main方法。

于 2010-04-06T06:33:23.497 回答
4

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

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

实现后者的一种方法是使用ServiceLoader.

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

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

for (Question q : ServiceLoader.load(Question.class)) {
    System.out.println(q);
}
于 2010-04-06T06:50:34.110 回答
2

为了运行静态初始化器,需要加载类。为此,您的“主”类必须(直接或间接)依赖于这些类,或者它必须直接或间接导致它们被动态加载;例如使用Class.forName(...).

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

这给您留下了两种选择:

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

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

于 2010-04-06T06:56:17.717 回答