2010-04-06 45 views
8

我有几类,如下所示获得静态初始化块在Java运行时不加载类

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类以便静态方法总是运行,所以当我运行我的主要方法时,我得到1而不是0。我不希望主要方法发生任何变化。

我实际上是试图实现工厂模式,其中子类注册工厂,但我简化了这个问题的代码。

回答

5

要在工厂注册TrueFalseQuestion类,需要调用其静态初始化程序。要执行TrueFalseQuestion类的静态初始值设定项,该类需要被引用,或者需要在调用QuestionFactory.map.size()之前通过反射进行加载。如果您想保持main方法不变,您必须引用它或通过静态初始化器中的反射来加载它。我不认为这是一个好主意,但我会回答你的问题:)如果你不介意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块之前。如果您不希望QuestionFactoryQuestion的实现有任何了解,则必须将它们列在配置文件中,该配置文件由QuestionFactory加载。我唯一能想到的方法是通过整个类路径来查看实现Question的类:)如果所有实现了Question的类都被要求属于同一个包,那么这可能会更好 - 注:我不认可这个解决方案;)

我不认为在QuestionFactory静态初始化做任何这样做的原因是因为喜欢TrueFalseQuestion类有自己的静态初始化在调用QuestionFactory,这在点一个构造不完整的对象,这只是在寻求麻烦。有一个配置文件简单地列出了你想要知道如何构造的类,然后在构造函数中注册它们是一个很好的解决方案,但这意味着改变你的方法。

+0

作为参考,这个设计的目的是避免工厂需要了解问题类(这里:http ://stackoverflow.com/questions/2582357/augment-the-factory-pattern-in-java)。 – 2010-04-06 06:36:16

+1

我没有看到这个问题。有些东西需要了解Question接口的实现,无论是直接的工厂,还是通过某种配置文件。正如我所说的,唯一的另一种方式是遍历类路径中的所有类并查看它们是否实现了问题。在上面的回答中,还要注意关于建立一个不完整的工厂的警告。它现在可能会奏效,但对于未来对象的状态(甚至是现在,跨平台)没有任何保证。 – 2010-04-06 06:52:51

6

您可以拨打:

Class.forName("yourpackage.TrueFalseQuestion"); 

这将加载类没有你实际接触它,并且将执行静态初始化块。

+0

我在哪里调用此方法?每个班级都在不同的文件中。 – randomThought 2010-04-06 05:51:21

+0

,然后再确实需要运行'TrueFalseQuestion'初始化程序。在你的例子中 - 在主要方法的开头 – Bozho 2010-04-06 05:52:36

+0

有没有什么办法可以在不改变主要方法中的任何东西的情况下实现这一点,因为这会在我想避免的主要方法中创建这种类的依赖关系。 – randomThought 2010-04-06 05:55:17

3

如果从未加载类,则无法执行该类的静态初始值设定项。

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

完成后者的一种方法是使用ServiceLoader

随着ServiceLoader你只需将文件放在META-INF/services/package.Question并列出所有的实现。你可以有多个这样的文件,每个.jar文件一个。通过这种方式,您可以轻松地将额外的Question实现与主程序分开。

QuestionFactory然后你可以简单地使用ServiceLodaer.load(Question.class)得到ServiceLoader,它实现Iterable<Question>,并可以这样使用:

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

为了运行静态初始化,类需要加载。要发生这种情况,您的“主”类必须(直接或间接)依赖于类,或者它必须直接或间接地使它们动态加载;例如使用Class.forName(...)

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

这使你两个选择:

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

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