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

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