Java中物件的生與滅- 核心篇

湯圓學Java發表於2021-04-12

1096379

前言

大家好啊,我是湯圓,今天給大家帶來的是《Java中物件的生與滅- 核心篇》,希望對大家有幫助,謝謝

文章純屬原創,個人總結難免有差錯,如果有,麻煩在評論區回覆或後臺私信,謝啦

比個耶

簡介

前面我們瞭解了Java的三大特性,其中介紹了類的繼承、過載等,這裡我們就基於這些知識點,把物件的建立和回收進行一個簡單的介紹

這篇不是很長,只是介紹核心的幾個知識點,相信大家很快就可以看完,真的

目錄

  • 堆和棧
  • 建構函式(生)
  • 物件的回收(滅)

正文

堆(heap)和棧(stack)

堆是一塊記憶體,用來存放物件

棧是另一塊記憶體,用來執行方法並儲存區域性變數,遵循後進先出的原則;

PS:棧並不儲存方法,只是執行方法,執行完方法後,會將方法彈出棧(方法存在方法區)

下面我們用實際程式碼,來看下堆和棧的區別

程式碼如下:

public class LiveAndDeathDemo {
  	// 基本型別屬性
    private int a;

    public static void main(String[] args) {
        LiveAndDeathDemo live = new LiveAndDeathDemo(1);
        live.fun();
    }
    public void fun(){
        int temp = 10;
        System.out.println(temp);
    }

    public LiveAndDeathDemo(int a) {
        this.a = a;
    }
    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}

可以看到,有一個例項變數a(堆), 兩個方法main和fun,其中fun有一個區域性變數temp(棧)

它們的區別如下所示:

棧 堆

這裡簡單介紹一下上面的流程

  1. main方法壓入棧中,建立區域性變數live(物件的引用)
  2. 建立物件live,在堆中開闢記憶體,將live放入堆中
  3. live呼叫fun方法,將fun壓入棧中(此時fun在棧頂)
  4. fun執行完成,出棧,繼續執行main方法
  5. 最後main方法執行完成,也出棧,程式結束

這裡可能有朋友要問了,那如果屬性是一個引用呢?它要存放在哪裡?

引用存放在堆裡,引用指向的物件也存放在堆裡,只不過是堆的另一個地方

如下圖所示:堆中 live物件的屬性 liveRef 指向了另一個物件(live物件2)

堆 引用

為啥要先介紹堆和棧呢?

因為堆和棧跟物件的生活息息相關

如果用人來比作物件的話,那堆就是人的家,棧就是外面的世界

我們出生在家裡,跟外面的世界打交道,最後在家裡。。。

物件的建立(生)

生存還是毀滅,這是一個問題。

​ -- 莎士比亞《哈姆萊特》

在Java的花花世界中,這也是個問題,不過是個有答案的問題;

答案就在下面。。。

往下看2

這裡我們先把問題簡化

因為我們最常見的建立物件是通過new建立,而new物件的核心就是通過建構函式來實現,所以我們這裡簡單起見,著重介紹建構函式,其他的後面等到虛擬機器部分再介紹

建構函式的分類:

  • 無參建構函式

  • 有參建構函式

建構函式和普通方法的區別

  1. 建構函式沒有返回型別

  2. 建構函式名與類名一致

關於編譯器的預設操作

  1. 如果沒有定義建構函式,編譯器會預設建立一個無參建構函式
  2. 如果子類定義了有參建構函式,且沒有顯示呼叫父類的建構函式,則編譯器預設呼叫父類的無參建構函式

當你自己有建立建構函式(無參或有參)時,編譯器都不會再去建立建構函式

建構函式的過載:

很常用,一般用來設定屬性的預設值

具體做法就是多個建構函式層層呼叫(又來套娃了)

下面舉個例子:

public class LiveAndDeathDemo {
    private int a;
    private String name;
		
    public LiveAndDeathDemo(){
        this(1);
    }
    public LiveAndDeathDemo(int a) {
        this(a, "JavaLover");
    }
    public LiveAndDeathDemo(int a, String name) {
        this.a = a;
        this.name = name;
    }
  // 省略getter,setter
}

用圖表示的話,就是下面這個樣子

建構函式 呼叫層級

建構函式的私有化

如果建構函式私有化,那麼它要怎麼用呢?

私有化說明只有類本身可以呼叫,這種主要用在工廠方法中

比如Java中的LocalDate,原始碼如下:

public final class LocalDate
        implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable {
	// 建構函式私有化 
  private LocalDate(int year, int month, int dayOfMonth) {
        this.year = year;
        this.month = (short) month;
        this.day = (short) dayOfMonth;
    }
  // 對外提供一個靜態方法,用來建立物件例項
   public static LocalDate of(int year, int month, int dayOfMonth) {
        YEAR.checkValidValue(year);
        MONTH_OF_YEAR.checkValidValue(month);
        DAY_OF_MONTH.checkValidValue(dayOfMonth);
        return create(year, month, dayOfMonth);
    }
}

這種用法在LocalDate這種工具類中用的比較多,還有就是單例模式(後面設計模式時再介紹)

上面介紹的建構函式沒有介紹到父類,下面開始介紹

如果有父類,建構函式的有哪些不一樣的地方

this和super:

在介紹父類的建構函式之前,有必要介紹下這個super

  • this指向當前類

  • super指向父類

super用來顯式呼叫父類相關屬性和方法(包括建構函式)

比如super.filedA, super.fun()

這裡有個特例,如果是在子類的建構函式中或者覆寫方法中,則直接呼叫super()即可呼叫父類對應的建構函式或方法(下面程式碼有演示)

建構函式的執行順序

  1. 如果子類Dog繼承父類Animal,那麼會先呼叫父類的建構函式,再呼叫子類的建構函式

  2. 如果父類Animal上面還有父類,會繼續往上呼叫

  3. 上面這個過程就叫做“建構函式鏈

這個關係有點像是:子女和父母的關係,子女要想出生,必須先讓爺爺奶奶把父母生出來,然後父母才能生子女

所以這裡如果我們要構造子類,必須先構造父類;如果父類還有父類,則繼續延伸,一直到Object超類為止

下面用程式碼演示下這個層層呼叫的過程:

public class SuperDemo extends Father{
    public SuperDemo() {
        // 1.1 這裡顯示呼叫父類的建構函式
        // 1.2 Super必須放在建構函式的第一行
        super();
        System.out.println("sub construct");
    }

    public static void main(String[] args) {
        SuperDemo demo = new SuperDemo();
    }
}
class Father{
    public Father() {
        // 2. 這裡沒有顯示呼叫父類(Object)的建構函式,編譯器會自己去呼叫
        System.out.println("father construct");
    }
}
/** 假設下面這個Object就是我們的超類
class Object{
	public Object(){
			// 3. 最終的建構函式,會調到這裡為止
	} 
}
**/

輸出如下:

father construct
sub construct

可以看到,先呼叫父類Father的建構函式,再呼叫子類的建構函式

他們之間的繼承關係如下:

SuperDemo的繼承關係

圖示說明:

左邊的虛線表示層層往上呼叫,直到超類Object

右邊的實現表示上面的構造完成會回到下面那一層,繼續構造,直到當前類

好了,構造的過程大致就是這個樣子了,還有很多其他方面的細節(比如類的初始化等)這裡先不介紹了,太多了,放到後面介紹

物件的回收(滅)

物件的回收是在程式記憶體不夠用時,將沒用的物件(可回收)進行釋放的一種操作,這個操作是由垃圾收集器GC來完成的

什麼是沒用的物件?

沒用的物件就是可回收的物件

說人話:當指向物件A的最後一個引用ref消失時,這個物件A就會變成沒用的物件,等待著垃圾收集器的回收

那怎麼才算引用消失呢?

基本分為兩種情況:

  • 如果引用是區域性變數,那當引用所在的方法執行完畢時,引用就會被釋放,那麼該物件隨即也就會被標記為沒用的物件,等待回收

  • 當引用指向其他物件或者null時,該物件會被標記為沒用的物件,等待回收

上面都是假設引用是指向物件的最後一個引用的情況,如果有多個引用指向同一個物件,那麼要等到引用都消失,物件才會被標記為可回收,即沒用的東西

總結

  1. 堆和棧

堆存放物件,棧用來執行方法並存放區域性變數

  1. 物件的建立

主要通過建構函式來建立,比如new物件

如果是反序列化來建立物件,則不會構造,因為構造後,物件的屬性會被重新初始化,那麼序列化的屬性值就被抹掉了(前面的Java中的IO流有涉及)

如果子類有父類,則先呼叫父類的建構函式,如果父類還有父類,則依次類推,直到Object超類

  1. 物件的回收

當指向物件的最後一個引用消失時,這個物件就會變成沒用的物件,等待著垃圾收集器的回收

後記

最後,感謝大家的觀看,謝謝

相關文章