【问题标题】:get static initialization block to run in a java without loading the class获取静态初始化块以在 java 中运行而不加载类
【发布时间】:2011-02-04 16:32:32
【问题描述】:

我有几个类,如下所示

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 方法有任何变化。

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

【问题讨论】:

    标签: java static-members static-initializer


    【解决方案1】:

    您可以致电:

    Class.forName("yourpackage.TrueFalseQuestion");
    

    这将在您不实际接触它的情况下加载该类,并将执行静态初始化程序块。

    【讨论】:

    • 我在哪里调用这个方法?每个类都在不同的文件中。
    • 在您真正需要运行 TrueFalseQuestion 初始化程序之前。在您的示例中 - 在 main 方法的开头
    • 有什么方法可以在不更改主方法中的任何内容的情况下实现这一点,因为这会在我想要避免的主方法中创建此类的一种依赖关系。
    • 在这种情况下,您没有编译时依赖项。它只是一个运行时依赖项。根据您“注册”子类的方式,您可以进行适当的检查。
    • @TP:如果你可以枚举一个包中的所有类,你可以查询所有实现Question 的类的名称并执行此操作。由于这无法完成(参见此处:stackoverflow.com/questions/520328/…),您只能手动检查 jar(呃)或使用某种配置文件...
    【解决方案2】:

    要向工厂注册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 这样的类有自己的静态初始化器,它调用 QuestionFactory,此时它是一个不完全构造的对象,这只是自找麻烦。拥有一个简单地列出您希望 QuestionFactory 知道如何构造的类的配置文件,然后在其构造函数中注册它们是一个很好的解决方案,但这意味着更改您的 main 方法。

    【讨论】:

    • 作为参考,这个设计是为了避免工厂需要知道问题类(这里:stackoverflow.com/questions/2582357/…)。
    • 我没有看到这个问题。需要了解 Question 接口的实现,无论是直接通过工厂还是通过某种配置文件。正如我所说,唯一的另一种方法是遍历类路径上的所有类并查看它们是否实现了 Question。还要注意我在上面的回答中关于有一个不完整的工厂的警告。它现在可能工作,但不能保证对象在未来(甚至现在,跨平台)的状态。
    【解决方案3】:

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

    因此,您要么需要加载所有正确的类(这很困难,因为您在编译时并不了解它们),要么摆脱对静态初始化器的要求。

    后者的一种方法是使用ServiceLoader

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

    QuestionFactory 中,您可以简单地使用ServiceLodaer.load(Question.class) 来获得ServiceLoader,它实现了Iterable&lt;Question&gt;,可以这样使用:

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

    【讨论】:

      【解决方案4】:

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

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

      这给你留下了两个选择:

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

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

      【讨论】:

        猜你喜欢
        • 2010-11-05
        • 1970-01-01
        • 1970-01-01
        • 2011-02-26
        • 2011-07-04
        • 2011-01-26
        • 1970-01-01
        • 2011-01-01
        • 1970-01-01
        相关资源
        最近更新 更多