如果相应的类从未使用过,类加载器是否会加载类文件?

fek*_*kir 3 java classloader

为了使我的问题更清楚,请考虑以下用例:

假设有一个包允许在给定平台上进行一组操作,例如在 Windows 上编辑注册表的类。这个包在其他平台上不存在,因为在其他操作系统上没有等效的操作。

为了简单起见,考虑

窗口/Registry.java

package windows;

public class Registry {

  static Registry instance = null;
  static{
    System.out.println("print from static block");
  }

  private Registry() {
    System.out.println("Registry instance created!");
  }

  public static synchronized Registry getInstance() {
    if (null == instance) {
      instance = new Registry();
    }
    return instance;
  }

  public void foo() {
    System.out.println("foo called.");
  }
}
Run Code Online (Sandbox Code Playgroud)

以及我将有条件使用注册表的类:main/Main.java

package main;

import windows.Registry;

public class Main {

  public static void test1(boolean onWindows) {
    if (onWindows) {
      Registry instance = Registry.getInstance();
      System.out.println("We are on Windows: ");
      instance.foo();
    } else {
      System.out.println("We are somewhere else!");
    }
  }

  public static void main(String[] args) {
    System.out.println("Entered main");
    boolean onWindows = args.length > 0 ? Boolean.parseBoolean(args[0]) : false;
    test1(onWindows);
  }
}
Run Code Online (Sandbox Code Playgroud)

问题是,如果 中 没有显式执行任何函数或类Main.class,是否可以保证 中 的代码不会Registry.class被执行?

我能够在多个桌面平台和不同的 java 版本上测试此示例,但我想知道此行为是否已记录并且可以依赖它,如果它因此也是其他平台(例如 android 或嵌入式)上的预期行为JRE 的版本。

如果没有这样的保证,因为(可能是自定义的)类加载器可能决定加载.class类路径中的所有文件,我是否可以假设代码将在没有java.lang.NoClassDefFoundErrorif为 false 并从类路径中onWindows删除的情况下工作?Registry.class

到目前为止我观察到的行为是

rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && java -classpath bld main.Main true
Entered main
print from static block
Registry instance created!
We are on Windows: 
foo called.

rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && java -classpath bld main.Main false
Entered main
We are somewhere else!


rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && rm -r bld/windows && java -classpath bld main.Main false
Entered main
We are somewhere else!

rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && rm -r bld/windows && java -classpath bld main.Main true
Entered main
Exception in thread "main" java.lang.NoClassDefFoundError: windows/Registry
        at main.Main.test1(Main.java:9)
        at main.Main.main(Main.java:20)
Caused by: java.lang.ClassNotFoundException: windows.Registry
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
        ... 2 more
Run Code Online (Sandbox Code Playgroud)

这些行为(惰性类加载器和删除Registry.class)是否已定义?

Joh*_*ger 7

问题是,如果 中 没有显式执行任何函数或类Main.class,是否可以保证 中 的代码不会Registry.class被执行?

这并不直接涉及类加载,而是涉及类初始化,这是执行相关类中任何代码的第一个点。具体来说,静态初始化块和静态成员的初始化器在此阶段执行。此时,所讨论的类必须已被加载并验证,但它可能已提前加载任意时间。

根据JLS 12.4.1

类或接口 T 将在第一次出现以下任一情况之前立即初始化:

  • T 是一个类,并且创建了 T 的实例。

  • static调用 T 声明的方法。

  • staticT 声明的字段被赋值。

  • 使用了 T 声明的字段static并且该字段不是常量变量

因此,如果您从未实例化该类或访问其任何静态方法或字段(除了读取作为“常量变量”的静态字段),则该类中的任何代码都不会被执行。

但是,类未初始化并不意味着不会尝试加载。JLS 并不禁止实现前瞻性地加载类。事实上,JLS 12.2.1具体说:

类加载器可以缓存类和接口的二进制表示,根据预期的使用情况预取它们,或者一起加载一组相关的类

main.Main因此,不,当类无法加载时,依赖类代表的应用程序运行而不会出现java.lang.NoClassDefFoundError或其他加载错误是不安全的windows.Registry,无论它是否可以预期实际使用。但是,在适当的情况下,您可以依赖未初始化的类,因此不会执行其中的任何代码。