Java中final修饰的变量的初始化时机

132 阅读5分钟

预备知识

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修饰的其他引用类型常量,赋值步骤和第二点的流程是一样的。

参考

blog.csdn.net/qq_43842093…

学后检测

一、单选题

  1. 关于 ConstantValue 属性,下列哪项描述是正确的

    A. 任何类型的静态变量都能通过 ConstantValue 属性赋值
    B. 只有被 static final 修饰、且类型为基本类型或 String 的字段,才会在准备阶段赋值
    C. 所有 final 字段都会在类加载的准备阶段赋值
    D. 只有通过方法赋值的 static final 字段才有 ConstantValue 属性

    答案:B
    解析:ConstantValue 只能用于 static final,且是基本类型或 String 且用字面量赋值的字段,赋值时机为准备阶段。

  2. 下列哪一条代码声明,会在类加载的“准备阶段”被赋值为具体值

    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 赋值。


二、多选题

  1. 以下哪些字段的赋值属于“初始化阶段”而非“准备阶段”?(多选)

    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 属于准备阶段。

  2. 下列哪些关于 Java 类成员变量赋值时机的说法是正确的?(多选)

    A. final 实例变量在实例初始化时赋值
    B. static 成员变量在类的准备阶段赋初值,在初始化阶段再赋具体值
    C. static final 基本类型用字面量赋值在准备阶段就赋值
    D. 所有 static final 字段都在初始化阶段赋值

    答案:A、B、C
    解析:D 错在“所有”,只有常量(字面量基本类型/String)才在准备阶段赋值。


三、判断题

  1. ( ) JVM 规范要求 static final 基本类型常量一定要在准备阶段赋值。

    答案:错
    解析:JVM 规范要求在初始化阶段赋值,但如 HotSpot JVM 实现中会提前到准备阶段。

  2. ( ) final static Object 类型字段无法用 ConstantValue 属性初始化。

    答案:对
    解析:只有基本类型或 String 能用 ConstantValue 属性赋值,引用类型(Object等)不行。


四、简答题

  1. 简述 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
    

五、编程题

  1. 编写 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 在实例初始化阶段赋值。