1. 程式人生 > >【Java程式設計思想】5.初始化與清理

【Java程式設計思想】5.初始化與清理

構造器就是在建立物件的時候被自動呼叫的特殊方法。

Java 在提供構造器的基礎上,額外提供“垃圾回收器”。控制物件從生命週期開始到結束。

5.1 用構造器確保初始化

建立物件時,Java 會在使用者有能力操作物件之前自動呼叫構造器,保證初始化進行。

5.2 方法過載

方法過載的存在,一方面是方法上的需要,另一方面也是為了可以定義多種構造器而存在。

過載的方法必須擁有獨一無二的引數型別列表。

當方法過載中的引數牽扯到基本型別時,

  • 常數 5 會被當做 int 處理,同理其他常量也是一致的。
  • 如果傳入資料型別(實際引數型別)小於方法中宣告的形式引數型別,實際資料型別就會被提升。
  • 如果傳入的實際引數大於過載方法宣告的形式引數,就得通過型別轉換執行窄化轉換。

不要以返回值區分過載方法—>因為在呼叫的時候不能指定型別資訊,編譯器不知道要呼叫哪個函式。雖然結合上下文有可能去判斷,但是在忽略返回值的呼叫中還是不能判斷的。

5.3 預設構造器

當一個類沒有定義任何構造器時,在建立這個類的物件時,會呼叫其預設構造器。 但是當定義了有參構造器時,除非重新宣告無參構造器,否則不能呼叫預設構造器去建立物件。

5.4 this 關鍵字

this 只能在方法內部使用,標識對“呼叫方法的那個物件”的引用。

return this // 當需要返回對當前物件的引用時,可以這樣使用

同一個類中,在一個構造器中呼叫另一個構造器也可以使用 this,不過不能呼叫兩個。同時必須將構造器置於起始處,否則編譯器會報錯。

static 方法就是沒有 this 的方法。在 static 方法內部不能呼叫非靜態方法。 static 的主要用途就是在沒有建立任何物件的前提下,僅僅通過類本身來呼叫 static 方法。從語義上講,這是“全域性方法”的一種實現(Java 中禁止使用全域性方法)。

5.5 清理:終結處理和垃圾回收

GC 三大原則: 1.物件可能不被垃圾回收 2.垃圾回收並不等於“析構” 3.垃圾回收只與記憶體有關

建立了足夠多的物件之後,記憶體耗盡,其中的祕密在於垃圾回收期的介入,Java 虛擬機器採用一種自適應的垃圾回收技術

前提: 對任何活的物件,一定能最終追述到其存貨在堆疊或靜態儲存區中的引用。從堆疊和靜態儲存區開始遍歷所有引用,並追蹤其所引用的物件,反覆進行直到根源於堆疊和靜態儲存區的引用所形成的網路全部被訪問位置----互動自引用的物件組不會被發現,就被自動回收了

自適應的垃圾回收技術-處理找到的存活物件:

  • 停止 - 複製:暫停程式執行,將所有存活物件從當前堆複製到另一個堆,沒有被複制的全部是垃圾。物件在新堆中一個愛著一個,保持緊湊排列
  • 標記 - 清掃:沒有新垃圾或很少有垃圾產生時,從堆疊和靜態儲存區出發,遍歷所有的引用,進而找出所有存貨的物件。每找到一個就為其設定一個標記,在全部標記工作完成後開始清理。清理過程中沒有標記的物件會被釋放,不會發生任何複製操作。–如此剩下不連續的堆需要在此進行 “停止 - 複製” 垃圾回收

記憶體分配以較大的“塊”為單位,物件較大的時候,會佔用單獨的塊。每個塊都用相應的“代數(genenration count)”來記錄其是否存活----如果塊在某處被引用,其代數會增加;GC 將對上次回收動作之後新分配的塊進行整理。如此這般,GC 就會定期進行完整的清理動作,大型物件仍然不會被複制,只是其代數會增加(嚴格來說,這是區別於“停止-複製”的要求的,節省了大量複製大型物件到新堆的時間),內含小型物件的塊會被複制並整理。

總的來說,JVM 會進行監視,物件都很穩定,GC 效率降低,會切換到“標記-清掃”模式;JVM 會跟蹤“標記-清掃”的效果,在堆空間出現很多碎片後會切換回“停止-複製”模式。這就是“自適應技術”

5.7 構造器初始化

類的沒個基本型別資料成員保證都會有一個初始值 初始化的順序是先靜態物件(如果他們尚未因前面的物件建立過程而初始化),而後是非靜態物件

總結一下物件的建立過程,以名為 Dog 的類舉例:

  1. 即使沒有顯式的使用 static 關鍵字,構造器實際上也是靜態方法。因此,當首次建立型別為 Dog 的物件時,或者 Dog 類的靜態方法或靜態域首次被訪問時,java 直譯器都會查詢類路徑,定位 Dog.class 檔案
  2. 載入 Dog.class,有關靜態初始化的所有動作都會執行—靜態初始化只在 class 物件首次載入的時候進行一次
  3. 當用 new Dog() 建立物件的時候,首先將在堆上為 Dog 物件分配足夠的儲存空間
  4. 這塊儲存空間會被清零,這就自動的將 Dog 物件中的所有基本型別資料都設定成了預設值,而引用被設定成了 null
  5. 執行所有出現於欄位定義處的初始化動作
  6. 執行構造器

5.8 陣列初始化

編譯器不允許指定陣列的大小——就是說,在初始化陣列後,我們擁有的只是對陣列的一個引用,並沒有給陣列物件本身分配任何空間。 為了給資料分配儲存空間,必須寫初始化表示式。

int[] a1 = {} //{}->儲存空間的分配(等價於使用 new)將由編譯器負責

可以直接使用 new 在數組裡建立元素(即使是基本型別陣列,new 依然可以工作;但是不能用 new 建立單個的基本型別資料)

int[] a = new int[20] //陣列元素中的基本資料型別會自動初始化成空值(數字和字元是0,布林是 false)

如果建立了一個非基本型別的陣列(即物件陣列),那麼就建立了一個引用陣列

Integer[] a = new Integer[20] // 此時 a 還只是引用陣列,直到通過建立新的 Integer 物件,並把物件賦值給引用,初始化程序才算結束,如果忘記建立物件並試圖使用陣列中的空引用,會在執行時產生異常

可以使用陣列作為引數,獲得可變引數列表的效果。 由於所有的類都直接或者間接繼承於 Object 類,所以可以建立以 Object 陣列為引數的方法。

//Java SE5以前:
function(Object[] args){} // 方法定義
function(new Integer(13), new Integer(1)); //呼叫方法
//Java SE5以後:
function(Object... args){} // 方法定義
function(13, 1); //呼叫方法

有了可變引數,就不需要再顯式的編寫陣列語法了,當指定引數時,編譯器會主動填充陣列。同時此種方式也支援傳入空陣列作為引數傳遞給可變引數列表。

可變引數列表中,可以將不同型別混合在一起,自動包裝機制會有選擇的將 int 引數提升為 Integer。

在可變引數列表參與過載的過程中,編譯器會使用自動包裝機制來匹配過載的方法,最後呼叫最明配匹配的方法。但是在不使用引數呼叫 f() 時,編譯器就無法知道應該呼叫哪一個方法了(針對使用可變引數列表的方法而言)。 實際可以在方法中增加一個非可變引數來解決改問題。但是原則上,應該只在過載方法的一個版本上使用可變引數列表,或者根本不使用這種方式。

5.9 列舉型別

列舉 enum 也是類的一種,可以和 switch 一起使用。 使用 print 可以打印出列舉值。