從內存分析局部變量與成員變量的區別(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);
    }
}

 

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