目錄
前言
- 首先,咱們來看一道面試題,題目如下,求打印結果
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()
- Test T1:由於 T1 是局部變量,便在分配給 main 方法的棧中分配一塊空間給 T1,同時在方法區開闢了一塊空間存放類信息,也就是Test.class,加載靜態變量 s,初始化爲 0
- new Test():在堆中開闢了一塊空間存放實例對象,而具體存放的是什麼,就要看實例化過程中實例變量了
- 實例化:實例化的過程其實是在底層執行了 <init>() 方法,實例化方法其實是有實例變量、非靜態代碼塊,構造器等組成的
- 實例變量存儲:從實例變量可以看出,實例對象空間裏面存放的是 i 和 j 兩個變量,默認值爲 0,也就是第 3,4 行的兩個變量
- 非靜態代碼塊的執行:也就是第 5~10 行非靜態代碼塊的執行,會在分配給 main 方法的棧中分配一塊空間給非靜態代碼塊,裏面存儲了局部變量 i,初始化爲 1,i++後值爲 2;由於 j 是成員變量,而 j 默認值爲 0,所以 j++ 後值爲 2;s 由於是共享的,所以每次執行都會 +1,執行後值爲 1
- 釋放資源:執行完後,棧的資源便釋放,非靜態代碼塊中的 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)
- T1.test():
- 在棧中開闢了一塊空間存放 T1.test() 方法
- 在存放T1.test() 方法的空間裏面開闢一塊空間存放局部變量 j,初始值爲 10
- test() 方法執行:
- j++:局部變量自增,執行後值爲 11
- i++:就近原則,i 會去找成員變量,由於之前成員變量 i 默認初始化爲 0,所以自增後值爲 1
- s++:s 由於是共享的,所以每次執行都會 +1,執行後值爲 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);
}
}