Skip to content

Latest commit

 

History

History
88 lines (52 loc) · 4.41 KB

为什么成员变量不需要手动初始化,而局部变量需要手动初始化.md

File metadata and controls

88 lines (52 loc) · 4.41 KB

为什么成员变量不需要手动初始化,而局部变量需要手动初始化?成员变量、静态变量、局部变量存放在哪里?

在 Java 中我们可以将变量分为两大类:成员变量和局部变量。而成员变量又分为两大类:静态成员变量和非静态成员变量。

在 Java 语言中,没有赋值的变量是不能使用的,无论是成员变量还是局部变量,在使用之前都需要初始化,而我们经常在代码中看到成员变量没有手动初始化,也可以正常使用,这是因为成员变量在类的加载过程已经被初始化了。

类的加载过程被分为5个阶段:加载 -> 验证 -> 准备 -> 解析 -> 初始化,而不同的变量,在不同的阶段会被赋予不同的值。

静态成员变量

静态成员变量在类加载过程中会被赋值2次,比如下面这段代码:

public static int code = 3;
  • 第一次赋值是在准备阶段,赋上 JVM 给的默认值,这个阶段 code = 0
  • 第二次赋值是在初始化阶段,赋上在代码中声明的默认值,这个阶段 code = 3

静态成员变量存放在方法区。方法区在 JVM 内存结构中是一个非常重要的区域,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量等等。

方法区中有一个常量池,用来存储编译期间生成的字面量和符号引用。

  • 字面量:指由字母、数字等构成的字符串或者数字常量,包括文本字符串,final 的常量值等
  • 符号引用:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符

当程序运行起来,被加载到内存后,才会为这些常量分配内存地址,这时原来的常量池转变成了运行时常量池,在运行期间也可以将新的常量放入运行时常量池,比如 Stringintern 方法。

非静态成员变量

非静态成员变量在类加载过程中会被赋值1次。

public int age = 18;

在初始化阶段赋上在代码中声明的默认值,这个阶段 age = 18,对象实例化之后,该对象存放在 Java 堆中,没有赋值的话,默认就为 0。

局部变量

Java 方法以栈帧的形式,运行在虚拟机栈(Java 栈)中,栈是线程私有的,程序启动的时候,会创建一个 main 线程,操作系统会为每一个线程分配一段内存,线程创建的时候会创建一个虚拟机栈,虚拟机栈的生命周期和线程一样,线程结束了。虚拟机栈也销毁了。

每个 Java 方法对应一个个栈帧,所以方法开始和结束都是一个个栈帧入栈和出栈的过程。每个栈帧包括了:局部变量表、操作数栈、方法返回地址、动态链接、附加信息。

方法中的局部变量会存放在栈帧的局部变量中,局部便量表主要用于存放方法参数和方法内部定义的局部变量。例如下面的代码:

public class Main {
    public static void main(String[] args) {
        Main main = new Main();
    }
}

局部变量表存放了方法参数和方法中的局部变量。

局部变量和成员变量不一样,JVM 不会给局部变量一个默认值,所以我们在使用局部变量的时候需要手动初始化,否则是无法使用的。

为什么局部变量必须手动初始化?

主要原因为了防止引用到一个错误的值。

为了达到节省资源的目的,局部变量的空间是可以被复用的,如果一个变量超过了它的作用域,在作用域之后声明的变量,可以复用之前的变量的位置,如果我们不手动初始化,很可能访问到之前变量的内容。

public void byteCode() {
    boolean devEnv = true;
    if (devEnv) {
        int a = 1;
        int b = 2;
    }

    int c;
}

为了达到节省资源的目的,c 可能复用 a 或者 b 的位置,通过变量 c 可能会访问到 a 或者 b 的内容,并不是一个正确的值,如果我们主动初始化可以边面这类问题的出现。

为什么 JVM 不给局部变量一个默认值?

这里借用 Think in Java 作者 Bruce Eckel 的一句话来回答这个问题。

编译器可以为局部变量赋上一个默认值,但是未初始化的局部变量更有可能是程序员的疏忽,所以采用默认值范围会掩盖这种错误。因此强制程序员提供一个初始值,往往能够帮助找出程序里的缺陷。