1. 程式人生 > >顛覆你思維的靜態載入順序

顛覆你思維的靜態載入順序

一.關鍵字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.執行是按照程式碼順序執行的。