类加载器何时加载 java 中的接口?

When classloader loads interfaces in java?

我正在尝试了解 classloader 的工作原理。我构建了一个简单的测试应用程序,并在 -verbose:class 模式下 运行ning。我有一个接口和一个 class。

界面

public interface Animal {

    int h = 8;
}

Class

public class Elephant implements Animal {

    static int staticInt;

    static void initTest(){
        System.out.println("HELLO");
    }
}

我 运行 在主应用程序中执行以下命令并获得预期的输出。接口和 class 都加载到内存中,因为我已经访问了大象的静态字段 class

int i = Elephant.staticInt;

[0.795s][info][class,load] classloader.test.Animal source: file:/C:/study/class-loader/out/production/class-loader/
[0.795s][info][class,load] classloader.test.Elephant source: file:/C:/study/class-loader/out/production/class-loader/

当我 运行 下面的代码时,我看到只有 Animal class 被加载了,这也是我所期望的。

Animal.class.getClassLoader();

[0.864s][info][class,load] classloader.test.Animal source: file:/C:/study/class-loader/out/production/class-loader/

问题是当我只访问界面字段时,我在控制台中看不到 class 加载输出。接口加载是否不同?如果未加载整数值,我如何访问它?我找不到关于该主题的任何内容。

//When i run the below code i only get the value of the integer but not the class loading info 
System.out.println("The integer is "  + Animal.h);

The integer is 8

注意:所有三个案例都是 运行 单独

我记不太清楚了,但我认为这是因为 h 在一个界面中 "public static final"。这意味着常量只是在 Elephant.class 字节码内部重新定义,如果我没记错的话,这意味着此时不需要 Animal。

Java 编译器内联静态最终常量(接口中的 int h 是隐式静态和最终的),因此您的代码有效编译为:

System.out.println("The integer is "  + 8);

如您所见,不再有对 Animal 的引用。这就是它没有加载的原因。

Are interfaces loaded differently ?

实际上,没有。 (如果您尝试访问另一个 class 中的 class 的 public static final 字段,您会得到同样的结果。)

How can I access to the integer value if it is not loaded?

您指的是用常量表达式初始化的字段。表达式在编译时求值,值在使用时插入到字节码中。因此,使用this接口字段不会触发class加载。

JLS 的相关部分是:

直接查看字节码就可以看出发生了什么。例如 class:

public class Test {
    public void test() {
        System.out.println(Animal.h);
    }
}

它是字节码(为简洁起见只显示 test()):

  // access flags 0x1
  public test()V
   L0
    LINENUMBER 3 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    BIPUSH 8
    INVOKEVIRTUAL java/io/PrintStream.println (I)V
   L1
    LINENUMBER 4 L1
    RETURN
   L2
    LOCALVARIABLE this LTest; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

可以看到java bytecode instruction listings中定义的BIPUSH 8指令,其描述为:

push a byte onto the stack as an integer value

8 不会从任何地方加载,因为它是一个编译时常量,因此在编译时直接解析,因为它永远不会改变。

因此您的 Animal class 将永远不会被 class 加载程序加载。

同样在使用反编译器时(我使用 IntelliJ,它使用 Fernflower),结果 Test class 看起来像这样:

public class Test {
    public Test() {
    }

    public void test() {
        System.out.println(8);
    }
}