从内存分析局部变量与成员变量的区别(Java)

目录

前言

一、考点

二、分析

1、局部变量与成员变量的区别

2、代码分析

后记


前言

  • 首先,咱们来看一道面试题,题目如下,求打印结果
public class Test {
    static int s;
    int i;
    int j;
    {
        int i = 1;
        i++;
        j++;
        s++;
    }
    public void test(int j){
        i++;
        j++;
        s++;
    }
    public static void main(String[] args){
        Test T1 = new Test();
        Test T2 = new Test();
        T1.test(10);
        T1.test(20);
        T2.test(30);
        System.out.println(T1.i + "," + T1.j + "," + T1.s);
        System.out.println(T2.i + "," + T2.j + "," + T2.s);
    }
}
  • 答案如下:
2,1,5
1,1,5

如果是一眼就看破答案的大佬,大可出门右转,小弟就不耽误大佬的时间了,接下来就深入研究此题

一、考点

  • 就近原则
  • 变量分类
    • 成员变量:类变量、实例变量
    • 局部变量
  • 非静态代码块的执行:每次创建实例对象都会执行
  • 方法的调用规则:调用一次执行一次

二、分析

1、局部变量与成员变量的区别

【1】声明的位置

  • 局部变量:方法体{}中,形参,代码块{}中
  • 成员变量:类中方法外
    • 类变量:有 static 修饰
    • 实例变量:没有 static 修饰

在本题目中,局部变量有:

  • 第 6 行非静态代码块的局部变量 i
  • 第 11 行形参局部变量 j
  • 第 16 行形参局部变量 args
  • 第17/18行局部变量 T1,T2

成员变量有:

  • 第 2 行的类成员变量 s
  • 第 3,4 行的实例成员变量 i,j

 【2】修饰符

  • 局部变量:final
  • 成员变量:public、protected、private、final、static、volatile、transient

【3】存储位置

  • 局部变量:栈
  • 实例变量:堆
  • 类变量:方法区

【4】作用域

  • 局部变量:从声明处开始,到所属的 “}” 结束
  • 实例变量:在当前类中“this.”访问,在其他类中“对象名.”访问
  • 类变量:在当前类中“类名.”访问,在其他类中“类名.”访问

这里注意:在本题中,第 7 行如果是 this.i++,那么就是实例成员变量自增,i++ 则表示的是 i = 1 的自增(就近原则)

【5】生命周期

  • 局部变量:每一个线程每次调用执行都是新的生命周期
  • 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量都是独立的
  • 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量都是共享的

2、代码分析

咱们一行行的来执行分析,看看在内存中是如何运行的,首先从 main 开始,在栈中给 main 方法分配了一块空间,进入第一行:

【1】Test T1 = new Test()

  1. Test T1:由于 T1 是局部变量,便在分配给 main 方法的栈中分配一块空间给 T1,同时在方法区开辟了一块空间存放类信息,也就是Test.class,加载静态变量 s,初始化为 0
  2. new Test():在堆中开辟了一块空间存放实例对象,而具体存放的是什么,就要看实例化过程中实例变量了
  3. 实例化:实例化的过程其实是在底层执行了 <init>() 方法,实例化方法其实是有实例变量、非静态代码块,构造器等组成的
    1. 实例变量存储:从实例变量可以看出,实例对象空间里面存放的是 i 和 j 两个变量,默认值为 0,也就是第 3,4 行的两个变量
    2. 非静态代码块的执行:也就是第 5~10 行非静态代码块的执行,会在分配给 main 方法的栈中分配一块空间给非静态代码块,里面存储了局部变量 i,初始化为 1,i++后值为 2;由于 j 是成员变量,而 j 默认值为 0,所以 j++ 后值为 2;s 由于是共享的,所以每次执行都会 +1,执行后值为 1
  4. 释放资源:执行完后,栈的资源便释放,非静态代码块中的 i 又变为 0(T1.j 是成员变量,不释放,值为 1;静态变量共享,不释放,值为1)

执行后:T1.i = 0,T1.j = 1,s = 1

【2】 Test T2 = new Test()

这一步和上一步执行的是一样,不同的是静态变量 s 值 +1,执行后值为 2

执行后:T2.i = 0,T2.j = 1,s = 2

【3】T1.test(10)

  1. T1.test():
    1. 在栈中开辟了一块空间存放 T1.test() 方法
    2. 在存放T1.test() 方法的空间里面开辟一块空间存放局部变量 j,初始值为 10
  2. test() 方法执行:
    1. j++:局部变量自增,执行后值为 11
    2. i++:就近原则,i 会去找成员变量,由于之前成员变量 i 默认初始化为 0,所以自增后值为 1
    3. s++:s 由于是共享的,所以每次执行都会 +1,执行后值为 3
  3. 资源释放:执行完后,栈的资源便释放,test() 方法中的 j 又变为 0,而 i 是成员变量,不释放,值为 1,静态变量共享,不释放,值为3(此时成员变量 T1.j 值为 1)

执行后:T1.i = 1,T1.j = 1,s = 3

【4】 T1.test(20)

这一步和上一步执行的是一样,执行test() 方法,局部变量 j、成员变量 i、静态变量 s 都自增加 1;释放时,局部变量 j 释放,成员变量 T1.i 自增加 1,值为 2,静态变量 s 值 +1,执行后值为 4(此时成员变量 T1.j 值为 1)

执行后:T1.i = 2,T1.j = 1,s = 4

【5】T2.test(30)

这一步和上一步执行的是一样,执行test() 方法,局部变量 j、成员变量 i、静态变量 s 都自增加 1;释放时,局部变量 j 释放,成员变量 T2.i 自增加 1,值为 1,静态变量 s 值 +1,执行后值为 5(此时成员变量 T2.j 值为 1)

执行后:T2.i = 1,T2.j = 1,s = 5

 所以最后:T1.i = 2,T1.j = 1,s = 5
                   T2.i = 1,T2.j = 1,s = 5

后记

分析了这么多,不知道有没有真正理解呢,如果将这个题目改为如下代码,输出结果会是怎样呢?请自行分析

public class Test {
    static int s;
    int i;
    int j;
    {
        int i = 1;
        this.i++;
        j++;
        s++;
    }
    public void test(int j){
        this.i++;
        j++;
        s++;
    }
    public static void main(String[] args){
        Test T1 = new Test();
        Test T2 = new Test();
        T1.test(10);
        T1.test(20);
        T2.test(30);
        System.out.println(T1.i + "," + T1.j + "," + T1.s);
        System.out.println(T2.i + "," + T2.j + "," + T2.s);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章