类加载和初始化:Java静态final变量

3

Example.java

public class Example {

    static final int i = 10;
    static int j = 20;
    static {
        System.out.println("Example class loaded and initialized");
    }
}

Use.java

import java.util.Scanner;
public class Use {
    public static void main(String args[]){
        Scanner sc = new Scanner(System.in);
        int ch = 1;
        while(ch != 0) {
            System.out.print("Enter choice: ");
            ch = sc.nextInt();

            if (ch == 1) {
                System.out.println("Example's i = " + Example.i);
            } else if(ch == 2){
                System.out.println("Example's j = " + Example.j);
            }
        }
    }
}

当我使用java -verbose:class Use运行时,输入1,则输出为10,即常量i的值。但是Example类尚未加载。 然而,当我输入2时,仅在此时 Example类才会被加载到JVM中,正如verbose输出所显示的那样,然后在Example内部执行静态块,并初始化并打印出j的值。 我的问题是: 如果在另一个类Use中请求了一个类Example的静态final(常量)值,但直到那时类Example尚未加载到JVM中,那么该常量值是从哪里获取的? 何时以及如何初始化并存储静态final i 到JVM内存中?

2
Java可以在编译(javac)时跨类内联final static变量。这是为了减少类加载的数量。我记得几天前看到过一个非常相似的问题,我会看看能否找到它。 - PiRocks
1
旧问题但有相关信息:https://dev59.com/YHA75IYBdhLWcg3wAEDx - PiRocks
@PiRocks,你的解释很有道理。感谢你回答这个问题并找出旧问题的参考资料 :) 那么,我的理解正确吗:在编译后的Use.class文件中,“System.out.println(“Example's i =”+ Example.i)”实际上是以字节码形式的“System.out.println(“Example's i =”+ 10)”? - Harshit Rajput
1
没错。你可以使用 javap -v -p path/to/Use.class 命令来检查一下。 - PiRocks
太棒了!我现在检查了一下,确实是那样。谢谢伙计 :) - Harshit Rajput
1个回答

2
根据Java语言规范第12.4.1节(已加重标注):
一个类或接口类型T将在以下任何一种情况的第一次出现之前立即初始化: - T是一个类并且创建了T的实例。 - 调用了由T声明的静态方法。 - 分配了由T声明的静态字段。 - 使用了由T声明的静态字段,且该字段不是常量变量(§4.12.4)。

常量变量是一个使用常量表达式初始化的最终变量。在你的代码中,Example.i是一个常量变量,因此不会导致类被加载。

那么如果类没有被加载,这个值从哪里来呢?

语言规范要求编译器内联它的值。来自二进制兼容性 13.1章节:

对于一个常量变量(§4.12.4)的引用必须在编译时解析为常量变量初始化器所表示的值V。如果这样的一个字段是静态的,那么在二进制文件中的代码中不应该有任何对该字段的引用,包括声明该字段的类或接口。这样的一个字段必须始终被认为已经被初始化(§12.4.2);永远不能观察到该字段的默认初始值(如果与V不同)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接