顛覆你思維的靜態載入順序
一.關鍵字static
1.static可以修飾成員變數、成員方法,不能修飾建構函式,建構函式是在建立物件時使用的。而static是類的屬性。
2.當一個函式沒有訪問例項變數資料時,才能被static修飾。因為靜態不能訪問非靜態的,在靜態載入時,有可能還沒有建立物件,成員變數和成員方法都是和物件有關的。
二.靜態函式注意事項
1.被static修飾的函式稱為類函式,非靜態函式也稱為例項函式
2.static可以修飾成員變數、成員方法,但是不能修飾建構函式
3.靜態函式是在類載入的時候就在記憶體中載入完成,可以直接執行的函式。(只要是函式最終都要進入棧記憶體,方法是什麼時候呼叫,什麼時候執行,不呼叫不執行。)
4.非靜態函式在類載入完成之後,通過new在堆中開闢記憶體空間,然後通過物件呼叫函式。
5.靜態不能訪問非靜態
因為靜態函式在類載入完成可以直接通過類名呼叫,而這時有可能還沒有建立物件,非靜態函式是依賴物件的。
6.非靜態可以訪問靜態
因為當非靜態函式可以執行,那麼說明類中一定建立了物件,說明物件所在的類已經載入完成,那麼非靜態就可以訪問靜態。
三.類和物件的載入過程
1.類載入過程:
① JVM啟動,載入所需要的class檔案② JVM載入class檔案時,會把所有的靜態內容(靜態成員變數、靜態方法、靜態程式碼塊)都先載入到方法區中的靜態區中。
③ 靜態載入完成之後,JVM開始給所有的成員變數預設初始化,靜態成員變數開闢空間。
④ 當給類中的所有靜態成員變數預設初始化完成,開始按照程式碼的順序依次執行(遇到靜態程式碼塊就執行,遇到靜態成員變數就顯示初始化)
⑤ 靜態都執行完畢,類才徹底載入完成
2.物件的載入過程:
① 當類載入完成,使用new關鍵字建立物件,在堆給物件分配記憶體空間② 給物件所屬的類的非靜態成員變數分配空間並進行預設初始化
③ 在JVM自動調取建構函式時先執行隱式三步
- super()區訪問父類構造,對父類進行初始化
- 給非靜態成員變數進行顯示賦值
- 執行構造程式碼塊
⑤ 建構函式執行完畢,物件建立完成。
四.靜態記憶體圖解
StaticDemo類
package cn.jason03; /** * 這是static的用法 * @author Jason * */ class Demo { int x; static int y = 3; // 靜態程式碼塊 static { System.out.println("靜態程式碼塊"); } // 定義構造程式碼塊 { System.out.println("我是構造程式碼塊"); System.out.println("x=" + x); } //建構函式 public Demo() { } static void print() { System.out.println("y=" + y); } void show() { System.out.println("x=" + x + " y=" + y); } } class StaticDemo { public static void main(String[] args) { //類名呼叫print方法 Demo.print(); //建立物件 Demo d = new Demo(); //給成員變數x賦值 d.x = 10; //用物件呼叫show方法 d.show(); } }
StaticCode類
package cn.jason03;
/**
* 靜態屬性執行順序1
*
* @author Jason
*/
class StaticCode {
static int x = 10;
static int y = show();
static int show() {
System.out.println("show..........x = " + x);
System.out.println("show..........y = " + y);
return 100;
}
// 靜態程式碼塊
static {
System.out.println("靜態程式碼塊執行....y= " + y);
}
void print() {
System.out.println("....................");
}
}
public class StaticCodeDemo {
public static void main(String[] args) {
new StaticCode().print();
}
}
記憶體圖解:
五.顛覆思維的小題目
第一道題:package cn.jason07;
public class StaticInitTest {
/*static {
value = 10;
print("靜態程式碼塊");
}*/
static int value = getValue();
static { // 通過靜態初始化塊為name變數初始化
System.out.println("靜態程式碼塊中value的值=" + value);
name = "周杰倫";
}
static {
value = 10;
print("靜態程式碼塊");
}
static String name = "林青霞"; // 定義靜態變數
public static void print(String s) {
System.out.println("value的值=" + value + " " + "名字是:"+name);
}
public static int getValue() {
return ++value;
}
public static void main(String[] args) {
System.out.println("value的值:" + StaticInitTest.value);
System.out.println("name的值:" + StaticInitTest.name);
}
}
輸出結果:
靜態程式碼塊中value的值=1
value的值=10 名字是:周杰倫
value的值:10
name的值:林青霞
package cn.jason07;
public class StaticInitTest {
static {
value = 10;
print("靜態程式碼塊");
}
static int value = getValue();
static { // 通過靜態初始化塊為name變數初始化
System.out.println("靜態程式碼塊1中value的值=" + value);
name = "周杰倫";
}
/*static {
value = 10;
print("靜態程式碼塊2");
}*/
static String name = "林青霞"; // 定義靜態變數
public static void print(String s) {
System.out.println("value的值=" + value + " " + "名字是:"+name);
}
public static int getValue() {
return ++value;
}
public static void main(String[] args) {
System.out.println("value的值:" + StaticInitTest.value);
System.out.println("name的值:" + StaticInitTest.name);
}
}
輸出結果是:
value的值=10 名字是:null
靜態程式碼塊1中value的值=11
value的值:11
name的值:林青霞
注意:
- 靜態函式是在類載入的時候就在記憶體中載入完成,可以直接執行的函式。靜態屬性優先於物件存在的,靜態屬性是類所共享的,靜態成員變數賦值可以在定義靜態變數之前,但是輸出不能在定義靜態變數之前。(只要是函式最終都要進入棧記憶體,方法是什麼時候呼叫,什麼時候執行,不呼叫不執行。)
- 對於物件而言,棧記憶體的引用地址不是物件,僅僅是為堆記憶體中物件分配記憶體空間時隨機分配的地址值而已,真正的物件在堆記憶體。指向只是方便使用成員屬性。
- 凡是對於靜態可以不用建立物件,直接可以用類名呼叫。凡是對於非靜態,如要訪問非靜態成員方法和成員屬性,那麼需要想方設法建立物件來訪問。(為什麼說想方設法呢?比如非靜態的內部類,如果非靜態內部類被private修飾,那麼只能在外部類裡建立物件來訪問被private修飾的內部類屬性和行為,在外部類之外是不能訪問的。)
第二道題:
package cn.jason01;
/**
* 靜態載入順序
*
* @author Jason
*
*/
public class StaticTest {
public static void main(String[] args) {
staticFunction();
show();
}
static StaticTest st = new StaticTest();
static {
System.out.println("1");
}
{
System.out.println("2");
}
public StaticTest() {
System.out.println("3");
System.out.println("建構函式中的.....a=" + a + " b=" + b);
}
public static void staticFunction() {
System.out.println("4");
// System.out.println("b=="+b);
}
int a = 110;
static int b = 112;
public static void show() {
System.out.println("show..........b=" + b);
}
}
輸出結果:
2
3
建構函式中的.....a=110 b=0
1
4
show..........b=112
第二道題解析:
①執行時,JVM先載入main函式所在的類,也就是StaticTest類。載入就是把StaticTest類的位元組碼全部放在方法區,與此同時只有靜態成員變數先預設初始化了,其他都沒有執行只是放在裡面。然後按照程式碼順序進行執行。所以第一步先執行static
StaticTest st = new StaticTest()。
②在堆記憶體new StaticTest()開闢一個空間,隨機分配十六進位制地址值,非靜態成員變數這個空間在開闢一個小空間,預設初始化值,int a=0。
③現在JVM自動呼叫建構函式,所以public StaticTest() {}進棧記憶體,先執行隱式三步。隱式三步第二步給非靜態成員顯示初始化,這是int a=110;隱式三步第三步就是執行構造程式碼塊(反編譯之後一目瞭然),所以先輸出2。隱式三步執行完,現在執行構造程式碼塊中的其它程式碼,所以輸出3,建構函式中的.........a=110,b=0
④按照程式碼順序向下執行,那麼開始執行靜態程式碼塊,輸出1
⑤給靜態變數b顯示初始化,這時b=112,然後才呼叫方法,一定是先給靜態顯示初始化完畢才呼叫方法的(對於本題是這樣的),輸出4.如果把staticFunction函式中的註釋放開,如果b=112,那麼說明靜態變數顯示賦值在呼叫方法之前完成。放開註釋結果正是b=112,說明驗證是正確的。
⑥在呼叫show方法,這時也能驗證執行時按照程式碼的順序執行的。輸出show..........b=112
總結:
1.載入時只對靜態成員變數預設初始化值,其它內容都只加載不執行。
2.執行是按照程式碼順序執行的。