Java静态变量的初始化(static块的本质)

本文探讨了Java中静态变量的初始化过程,包括初始化方式、static块的处理及静态变量声明的特点,并给出了实例说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

public class Test {  
    static {  
        _i = 20;  
    }  
    public static int _i = 10;  

    public static void main(String[] args) {  
        System.out.println(_i);  
    }  
}  

上述代码会打印出什么结果来呢?10还是20?本文将以此代码为引子,着重讨论一下静态变量的初始化问题。

问题1:静态变量如何初始化

Java类中可以定义一个static块,用于静态变量的初始化。如:

public class Test {  
    public static int _i;  
    static {  
        _i = 10;  
    }  
}  

当然最常用的初始化静态变量的操作是在声明变量时直接进行赋值操作。如:

public class Test {  
    public static int _i = 10;  
}  

那么上述两例在本质上有什么区别吗?回答是没有区别。两例代码编译之后的字节码完全一致,通过 “javap -c”查看到的字节码如下:

public class Test extends java.lang.Object{
public static int _i;



public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

static {};
  Code:
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: return
}

通过字节码还可以看出,当类的定义中不含有static块时,编译器会为该类提供一个默认的static块。当然这是在含有静态变量初始化操作的前提下。如果静态变量没有初始化操作,则编译器不会为之提供默认的static块。如:

public class Test {  
    public static int _i;  
}  

其字节码的表现形式为:

public class Test extends java.lang.Object{
public static int _i;

public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

}

由于静态变量是通过赋值操作进行初始化的,因此可以通过静态函数返回值的方式为其初始化。如:

public class Test {  
    public static int _i = init();  

    private static int init() {  
        return 10;  
    }  
}  

其本质与下面的代码相同:

public class Test {  
    public static int _i;  
    static {  
        _i = init();  
    }  

    private static int init() {  
        return 10;  
    }  
}  

问题2:JDK如何处理static块

类定义中可以存在多个static块吗?回答是可以。如:

public class Test {  
    public static int _i;  
    static {  
        _i = 10;  
    }  

    public static void main(String[] args) {  
    }  

    static {  
        _i = 20;  
    }  
}  

此类编译之后的字节码为:

public class Test extends java.lang.Object{
public static int _i;



public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

public static void main(java.lang.String[]);
  Code:
   0: return

static {};
  Code:
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: bipush 20
   7: putstatic #2; //Field _i:I
   10: return

}

观察static{}部分可以看出,上例的代码与下面的代码效果一致:

public class Test {  
    public static int _i;  

    public static void main(String[] args) {  
    }  

    static {  
        _i = 10;  
        _i = 20;  
    }  
}  

此例可以证明,不仅类定义中可以有多个static块,而且在编译时编译器会将多个static块按照代码的前后位置重新组合成一个static块。

问题3:如何看待静态变量的声明

静态变量存放在常量池之中。如何证明呢?如:

public class Test {  
    public static int _i = 10;  
}  

使用“javap -c -verbose”查看其字节码的内容如下:

public class Test extends java.lang.Object
  SourceFile: "Test.java"
  minor version: 0
  major version: 49
  Constant pool:
const #1 = Method #4.#14; //  java/lang/Object."<init>":()V
const #2 = Field #3.#15; //  Test._i:I
const #3 = class #16; //  Test
const #4 = class #17; //  java/lang/Object
const #5 = Asciz _i;
const #6 = Asciz I;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz <clinit>;
const #12 = Asciz SourceFile;
const #13 = Asciz Test.java;
const #14 = NameAndType #7:#8;//  "<init>":()V
const #15 = NameAndType #5:#6;//  _i:I
const #16 = Asciz Test;
const #17 = Asciz java/lang/Object;


{
public static int _i;


public Test();
  Code:
   Stack=1, Locals=1, Args_size=1
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return
  LineNumberTable: 
   line 2: 0



static {};
  Code:
   Stack=1, Locals=0, Args_size=0
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: return
  LineNumberTable: 
   line 3: 0

}

我们看到,常量池中const #2指向的就是Test._i,也就是静态变量。静态变量被保存到常量池中的工作原理这里不深入讨论。在此需要注意的是:

静态变量的声明与初始化是两个不同的操作;
静态变量的声明在编译时已经明确了内存的位置。
如:

public class Test {  
    public static int _i = 10;  
}  

上述代码的本质可以视为:

public class Test {  
    // 静态变量的声明  
    public static int _i;  

    // 静态变量的初始化  
    static {  
        _i = 10;  
    }  
}  

由于静态变量的声明在编译时已经明确,所以静态变量的声明与初始化在编码顺序上可以颠倒。也就是说可以先编写初始化的代码,再编写声明代码。如:

public class Test {  
    // 静态变量的初始化  
    static {  
        _i = 10;  
    }  

    // 静态变量的声明  
    public static int _i;  
}  

对初始问题的解答
解答了上述三个问题,让我们再来看看开篇提到的问题。代码如下:

public class Test {  
    static {  
        _i = 20;  
    }  
    public static int _i = 10;  

    public static void main(String[] args) {  
        System.out.println(_i);  
    }  
}  

其本质可以用下面的代码表示:

public class Test {  
    static {  
        _i = 20;  
    }  
    public static int _i;  
    static {  
        _i = 10;  
    }  

    public static void main(String[] args) {  
        System.out.println(_i);  
    }  
}  

再简化一下,可以表示为:

public class Test {  
    public static int _i;  

    static {  
        _i = 20;  
        _i = 10;  
    }  

    public static void main(String[] args) {  
        System.out.println(_i);  
    }  
}  

至此,代码已经明确告诉我们打印结果是什么了!
这里写图片描述

参考:
https://blog.csdn.net/darxin/article/details/5293427(Java静态变量的初始化)

<think>嗯,用户想了解Java静态变量初始化机制。首先,我得回忆一下相关的知识点,可能包括静态变量的生命周期、初始化顺序、触发条件等等。根据用户提供的引用资料,特别是引用[3]提到的Java类的主动使用六种情况,静态变量初始化应该是在类被首次主动使用时进行的。比如创建实例、访问静态变量或方法、反射等。 用户提到的引用[1]和[2]都讨论了初始化顺序,静态变量和静态是在类加载的时候初始化的,而且顺序是按照它们在代码中出现的顺序来执行的。这点需要注意,可能有时候静态的位置会影响静态变量的值。 另外,引用[4]提到静态是将多个初始化语句合并在一起,本质上和分开写静态变量初始化是一样的。而引用[5]指出编译器会把所有静态初始化的代码合并成一个类初始化例程,按照代码顺序执行,并且只运行一次。这说明静态变量和静态的执行顺序很重要,因为它们会影响最终的结果。 用户的问题可能还涉及到静态变量默认值的问题。比如,如果没有显式初始化,基本类型会有默认值,如int是0,对象是null。但如果是显式初始化,比如静态变量赋值或者静态中的操作,那这些会在类初始化时执行。 还需要区分静态变量和实例变量初始化时机。静态变量属于类,在类加载时初始化;实例变量则在对象创建时初始化,每次new都会执行。 可能还需要提到被动使用和主动使用的区别,被动使用不会触发类的初始化,比如访问静态常量(编译期常量)可能不会触发,因为它们在编译时就被处理了。 总结一下,用户需要了解静态变量何时被初始化初始化的顺序,触发条件以及相关注意事项。应该分点回答,结构清晰,结合引用中的内容,比如主动使用的六种情况,初始化顺序中的静态变量和静态优先于实例变量和构造器等。</think>### Java静态变量初始化机制分步解析 Java静态变量初始化过程遵循严格的时序规则,其核心机制可分为以下几个关键点: #### 一、初始化触发条件 静态变量初始化在类**首次主动使用**时触发,包含6种情形[^3]: 1. 创建类实例(`new MyClass()`) 2. 访问/修改静态变量(`MyClass.staticVar = 5`) 3. 调用静态方法(`MyClass.staticMethod()`) 4. 反射操作(`Class.forName("MyClass")`) 5. 初始化子类(父类会被首先初始化) 6. 作为JVM启动类(`java MyClass`) #### 二、初始化执行顺序 当类被首次加载时,按照以下顺序执行[^1][^5]: ``` (静态变量声明 + 静态初始化)→(实例变量声明 + 实例初始化)→ 构造器 ``` 重要特性: 1. **声明顺序敏感**:多个静态变量初始化按代码书写顺序执行 2. **合并执行机制**:编译器会将所有静态初始化操作合并为单一代码[^5] 3. **单次执行特性**:类生命周期内仅初始化一次 #### 三、初始化方式对比 | 初始化方式 | 示例 | 执行时机 | |---------------------|-------------------------------|-----------------------| | 声明时初始化 | `static int a = 10;` | 类加载时按顺序执行 | | 静态初始化 | `static { a = 20; }` | 与声明合并为单一代码| | 延迟初始化 | 通过静态方法动态赋值 | 首次访问时触发 | #### 四、特殊情形处理 1. **未显式初始化**: - 基本类型:赋予默认值(如`int`为0) - 引用类型:初始化为`null` 2. **常量优化机制**: ```java static final int MAX = 100; // 编译期常量,不会触发类初始化 static final Date NOW = new Date(); // 运行时常量,会触发初始化 ``` 3. **继承场景**: ```java class Parent { static { System.out.print("A"); } } class Child extends Parent { static { System.out.print("B"); } } // 输出顺序:AB ``` #### 五、验证示例 ```java public class InitDemo { static int a = init("声明赋值"); static { a = init("静态1"); } static int b = init("静态后声明"); static { a = init("静态2"); } static int init(String msg) { System.out.println(msg); return 0; } public static void main(String[] args) { System.out.println("执行main方法"); } } ``` 输出顺序: ``` 声明赋值 静态1 静态后声明 静态2 执行main方法 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值