首页 > 解决方案 > 使用 java.lang.Class 初始化(加载)一个 java 类实例

问题描述

我不得不承认这更像是一个表面问题,但我还没有找到更直接的解决方案这一事实让我觉得我可能遗漏了一些东西。

问题是,我的类(比方说 Foo)有一个非常重要的static块,它Foo.class使用 a 中的构建器方法注册自己()Map,如下所示:

// somewhere in the class
static {
    Bar.registerBuilder(Foo.class, Foo::build);
}

这使得Foo从类中获取构建器成为可能Bar,有点像这样:

// somewhere in a method
Foo foo = Bar.getBuilder(Foo.class).apply("Hello World");

(如果构建器接受String参数)。但是,上面的代码示例只有在Foo类已经初始化的情况下才有效。如果不是,这意味着该staticFoo尚未执行,并且构建器现在尚未注册Bar,这将导致getBuilder()返回nullapply()抛出NullPointerException.

感谢互联网(主要是 StackOverflow),我发现您可以强制使用Class.forName(String). 但真正让我感到困惑的是,这个方法需要 a String(因此抛出了 checked ClassNotFoundException),而我还没有找到直接通过java.lang.Class实例加载和初始化类的方法。我本来期望像

Class<Foo> clazz = Foo.class;
clazz.load(); // does not exist

相反,我必须这样做:

Class<Foo> clazz = Foo.class;
try {
    Class.forName(clazz.getName());
} catch (ClassNotFoundException) {
    // handle an exception that is actually unreachable
}

我想知道我是否完全遗漏了某些东西,或者如果没有,是否有一种更简洁的方法可以通过java.lang.Class表示加载和初始化一个类。

任何帮助表示赞赏,谢谢!

编辑1:正如@Boris the Spider 在评论中指出的那样,Foo.class可能应该已经加载并初始化了该类,但它没有(至少在我的情况下),这就是我什至遇到这个问题的原因。

编辑2:使用“复杂”的方式来加载类Class.forName()(如代码示例中所示)实际上解决了我认为的问题。只是如果可能的话,我想使用一种更清洁的方式。

使用:

标签: javaclassreflectioninitialization

解决方案


如果您已经在引用该类,最好将该静态代码移到普通的静态工厂方法中。既然可以运行该方法,为什么还要使用反射或尝试引用某个类只是为了使某些代码运行?

public static BuilderFunction createBuilder() {
    return Foo::build;
}

只需在以下静态块中调用它Bar

registerBuilder(Foo.class, Foo.createBuilder());

如果你需要更动态的东西,你可以使用服务加载器,尤其是 java 9+,因为它们现在使用起来更好:

provides my.BuilderProvder with something.FooProvider;

只需将它们全部加载到 Bar 中:

ServiceLoader<BuilderProvder> loader = ServiceLoader.load(BuilderProvder.class);
loader.stream()
      .forEach(provider -> registerBuilder(provider));

现在,即使不是您开发的不同模块也可以提供自己的构建器,并且您不需要进行任何手动类加载(并且只有在实际使用类时才保证发生类初始化,例如使用某些方法或字段 - 请注意,常量是在编译时内联,因此它们不计算在内)。

您还可以使用一些 hacky 反射库,如 ClassGraph 或 Reflections 来获取给定类型/具有给定注释的所有类,然后加载它们并在它们上调用一些 init 方法,就像我第一个提出的解决方案一样createBuilder。这是spring内部注册了多少组件,类似的事情可以用java注解预处理在编译时找到这个类并保存名称。但如果可能的话,我建议坚持使用现有的内置解决方案,如服务加载程序。


推荐阅读