1. 程式人生 > >java程式設計思想讀書筆記二(物件的建立)

java程式設計思想讀書筆記二(物件的建立)

java物件

物件的建立

java的物件是在執行時建立的,建立物件的的觸發條件有以下幾種:

  1. 用new語句建立物件,這是最常用的建立物件方法。
  2. 運用反射手段,呼叫java.lang.reflect.Constructor類的newInstance()例項方法。
  3. 呼叫物件的clone()方法。
  4. 運用反序列化手段,呼叫java.io.ObjectInputStream物件的readObject()方法。

物件建立過程

java物件在建立時需要在方法區的執行時常量池去查詢該類的符號引用,如果沒有發現符號引用,說明該類還沒有被JVM載入,所以要先進行JVM的載入。當JVM載入完時,會在java堆中分配記憶體。分配記憶體時會根據堆記憶體是否規整來分別進行兩種方式的分配。
1.指標碰撞
把記憶體分為可用的和已用的,在中間放置一個指標,如果要分配物件的空間,則把記憶體的指標向可用的一端移動當前需要分配物件大小的距離。
2.空閒列表
當記憶體並不是很規整時,需要一個列表來維護那些列表是可用的,那些是不可用的。

當記憶體分配完成後需要對分配到記憶體空間的物件賦予零值(靜態欄位在類載入中就已經有值,所以不需要賦零值),接下來需要設定物件頭的資訊:如設定該物件的雜湊碼,屬於哪個類,GC分代年齡等資訊。從虛擬機器的角度來看至此一個物件就建立完成,但是在java程式的角度看,物件的建立才剛剛開始,因為物件的值還沒有設定,物件值得設定是由物件初始化化來完成的,初始化就是呼叫構造方法過程。

物件的初始化

當物件建立完成後,接下來就是進行物件的初始化了,也就是去執行構造方法。

父類的初始化

當子類物件建立之前首先會呼叫父類的建構函式,也就是會初始化父類,但是父類並沒有被建立,也就是並沒有在堆中給父類分配新的儲存空間,而只是對父類的變數進行了賦值。從而達到子類可以使用父類的屬性和方法的目的。

靜態類的初始化

當一個類中有static修飾的方法或者是變數的話,那麼當這個靜態方法被第一次呼叫的時候,那麼這個類就會初始化,就會呼叫此類的類構造器(在類載入過程中被JVM自動加入),類構造器只初始化一次,觸發條件為例項構造器執行,或者是靜態任何一個靜態成員被引用,也就是說這個類只會初始化一次。

普通類的初始化

區別與父類和靜態類的初始化,普通類的初始化是建立在物件建立之上的,也就是物件建立完成後會自動的去呼叫構造方法進行初始化。

物件建立及初始化例項

看完上面文字上的簡單說明總感覺少些什麼東西?就像一碗牛肉拉麵沒有滷雞蛋一樣。所以筆者要通過一個java程式碼來將上面的知識點串起來,讓你有一個更清晰的認識。


public class Parent {
    int a = 1;
    static int b = 2;

    // 靜態程式碼塊
    static {
        System.out.println("執行Parent靜態程式碼塊:b =" + b);
        b++;
    }

    // 普通程式碼塊
    {
        System.out.println("執行Parent普通程式碼塊: a =" + a);
        System.out.println("執行Parent普通程式碼塊: b =" + b);
        b++;
        a++;
    }

    // 無參建構函式
    Parent() {
        System.out.println("執行Parent無參建構函式: a =" + a);
        System.out.println("執行Parent無參建構函式: b =" + b);
    }

    // 有參建構函式
    Parent(int a) {
        System.out.println("執行Parent有參建構函式: a =" + a);
        System.out.println("執行Parent有參建構函式: b =" + b);
    }

    // 方法
    void fun() {
        System.out.println("執行Parent的fun方法");
    }

}

public class Child extends Parent {
    int c = 1;
    static int d = 2;
    // 靜態程式碼塊
    static {
        System.out.println("執行Child靜態程式碼塊:d =" + d);
        d++;
    }
    // 普通程式碼塊
    {
        System.out.println("執行Child程式碼塊: c =" + c);
        System.out.println("執行Child程式碼塊: d =" + d);
        c++;
        d++;
    }

    // 建構函式
    Child() {
        System.out.println("執行Child建構函式: c =" + c);
        System.out.println("執行Child建構函式: d =" + d);
    }

    // 方法
    void fun() {
        System.out.println("執行Child的fun方法");
    }

}

public class Test {
    public static void main(String[] args) {
        Child demo = new Child();
        demo.fun();
        System.out.println("…………………………………………………………………………………………………………………………");
        Child child = new Child();
        child.fun();
    }
}

上面有三個很簡單的類,一個Parent,一個Child,一個Test。當執行Test的main方法是會輸出什麼呢?

//輸出結果
執行Parent靜態程式碼塊:b =2
執行Child靜態程式碼塊:d =2
執行Parent普通程式碼塊: a =1
執行Parent普通程式碼塊: b =3
執行Parent無參建構函式: a =2
執行Parent無參建構函式: b =4
執行Child程式碼塊: c =1
執行Child程式碼塊: d =3
執行Child建構函式: c =2
執行Child建構函式: d =4
執行Child的fun方法
…………………………………………………………………………………………………………………………
執行Parent普通程式碼塊: a =1
執行Parent普通程式碼塊: b =4
執行Parent無參建構函式: a =2
執行Parent無參建構函式: b =5
執行Child程式碼塊: c =1
執行Child程式碼塊: d =4
執行Child建構函式: c =2
執行Child建構函式: d =5
執行Child的fun方法

下面我們一起來看看這些輸出是這麼一步步產生的。



注:本文的重點不是虛擬機器有關的詳細執行,之後會專門寫一個關於虛擬機器載入的文章,所以有關細節問題都一筆帶過了
歡迎大家來我的個人部落格 http://anning.site