预备知识
Java类的生命周期: 加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)
初始化时机
ConstantValue
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。 用final修饰不是在构造方法赋值的String类型或者基本类型成员变量,编译成字节码文件时,对应的字段表也会带有ConstantValue属性。但是只有被static关键字修饰的类变量才可以使用这项属性来进行初始化,否则使用这项属性也会被JVM忽略掉。
ConstantValue需要满足2个条件:
- 类型为基本类型或者String。
- 此类型被赋值时只能使用字面量而不是方法的形式。
final修饰成员变量
-
首先
final修饰成员变量(final单独修饰不和static一起使用)是在实例初始化的时候被赋值的。这个和非final修饰成员变量赋值时机没有什么不同,只不过final修饰的不能多次赋值罢了。 -
final static修饰的成员变量只有在其类型为ConstantValue时才会在准备阶段被赋予具体值(而不是类型的默认值)。
举例:
- final int a = 1;// 初始化赋值且使用的是字面量赋值。
- final static int a = 1;// 准备阶段赋值-基本类型可以转化为ConstantValue,且使用的是字面量赋值。
- final static int a = getA();// 初始化阶段赋值-基本类型可以转化为ConstantValue,但赋值不是使用字面量。
- final static String b = "abc";// 准备阶段赋值-String可以转化为ConstantValue,且使用的是字面量赋值。
- final static String b= getB();// 初始化阶段赋值-String可以转化为ConstantValue,但赋值不是使用字面量。
- final static Object c = new Object();// 初始化阶阶段赋值-其他类型不可以转化为ConstantValue
总结
- final修饰的实例属性,在实例创建的时候,也就是初始化阶段才会赋值。
- static修饰的类属性,在类加载的准备阶段赋初值,初始化阶段赋值。
- static+final修饰的String类型或者基本类型常量,JVM规范是在初始化阶段赋值,但是HotSpot VM直接在准备阶段就赋值了。
- static+final修饰的其他引用类型常量,赋值步骤和第二点的流程是一样的。
参考
学后检测
一、单选题
-
关于 ConstantValue 属性,下列哪项描述是正确的?
A. 任何类型的静态变量都能通过 ConstantValue 属性赋值
B. 只有被 static final 修饰、且类型为基本类型或 String 的字段,才会在准备阶段赋值
C. 所有 final 字段都会在类加载的准备阶段赋值
D. 只有通过方法赋值的 static final 字段才有 ConstantValue 属性答案:B
解析:ConstantValue 只能用于 static final,且是基本类型或 String 且用字面量赋值的字段,赋值时机为准备阶段。 -
下列哪一条代码声明,会在类加载的“准备阶段”被赋值为具体值?
A.
final int a = 2;
B.final static String s = getStr();
C.static final int k = 10;
D.static Object obj = new Object();答案:C
解析:只有 static final 且是基本类型或 String 字面量赋值,才会在准备阶段通过 ConstantValue 赋值。
二、多选题
-
以下哪些字段的赋值属于“初始化阶段”而非“准备阶段”?(多选)
A.
static int a = 10;
B.final int b = 3;
C.static final String s = getS();
D.static final int n = 100;答案:A、B、C
解析:A 是普通 static,B 是 final 实例变量,C 是 static final 但赋值不是字面量(用方法),这些都属于初始化阶段。只有 D 属于准备阶段。 -
下列哪些关于 Java 类成员变量赋值时机的说法是正确的?(多选)
A. final 实例变量在实例初始化时赋值
B. static 成员变量在类的准备阶段赋初值,在初始化阶段再赋具体值
C. static final 基本类型用字面量赋值在准备阶段就赋值
D. 所有 static final 字段都在初始化阶段赋值答案:A、B、C
解析:D 错在“所有”,只有常量(字面量基本类型/String)才在准备阶段赋值。
三、判断题
-
( ) JVM 规范要求 static final 基本类型常量一定要在准备阶段赋值。
答案:错
解析:JVM 规范要求在初始化阶段赋值,但如 HotSpot JVM 实现中会提前到准备阶段。 -
( ) final static Object 类型字段无法用 ConstantValue 属性初始化。
答案:对
解析:只有基本类型或 String 能用 ConstantValue 属性赋值,引用类型(Object等)不行。
四、简答题
-
简述 ConstantValue 属性作用及应用场景,并举例说明哪些字段会带有 ConstantValue 属性。
参考答案:
ConstantValue 属性用于在类的准备阶段由虚拟机自动为静态常量赋初值,提高效率。只有 static final 修饰的基本类型或 String,且直接用字面量赋值的字段会有 ConstantValue 属性。
例如:static final int A = 1; // 有 ConstantValue static final String S = "hello"; // 有 ConstantValue static final int B = getB(); // 没有 ConstantValue
五、编程题
-
编写 Java 代码说明如下三种字段赋值时机的不同,并输出结果验证:
- final int a = 1;
- static final int b = 2;
- static final int c = getC();
public class ConstTest { final int a = 1; static final int b = 2; static final int c = getC(); static int getC() { System.out.println("getC() called"); return 3; } public static void main(String[] args) { System.out.println("b: " + b); System.out.println("c: " + c); ConstTest ct = new ConstTest(); System.out.println("a: " + ct.a); } }解析:
- b 在准备阶段赋值(通过 ConstantValue),c 在初始化阶段调用 getC() 方法赋值(会输出 getC() called),a 在实例初始化阶段赋值。