Java中的析構方法finalize
在C++程式設計中有建構函式與解構函式的概念,並且是記憶體管理技術中相當重要的一部分,而在Java語言中只有構造器(也可以稱為建構函式)的概念,卻沒有析構器或解構函式的概念。這是因為,理論上JVM負責對象的析構(銷毀與回收)工作。也就是上面講到的記憶體回收的概念。那麼Java語言中是否真的不存在與C++中解構函式職能類似的方法。其實Java語言中的finalize 方法與C++語言中的解構函式的職能就極為類似。finalize方法是Java語言根基類Object類中所包含的一個方法,這個方法是保護類型的方法(protected),由於在Java應用中開發的所有類都為Object的子類,因此使用者類都從Object對象中隱式地繼承了該方法。因此,我們在Java類中可以調用其父類的finalize方法,並且可以覆蓋自身繼承來的finalize方法。雖然我們可以在一個Java類中調用其父類的finalize方法,但是由於finalize方法沒有自動實現遞迴調用,我們必須手動實現,因此finalize函數的最後一個語句通常是super.finalize()語句。通過這種方式,我們可以實現從下到上finalize的迭代調用,即先釋放使用者類自身的資源,然後再釋放父類的資源。通常我們可以在finalize方法中釋放一些不容易控制,並且非常重要的資源,例如:一些I/O的操作,資料的串連。這些資源的釋放對整個應用程式是非常關鍵的。
finalize方法最終是由JVM中的記憶體回收行程調用的,由於記憶體回收行程調用finalize的時間是不確定或者不及時的,調用時機對我們來說是不可控的,因此,有時我們需要通過其他的手段來釋放程式中所佔用的系統資源,比如自己在類中聲明一個destroy()方法,在這個方法中添加釋放系統資源的處理代碼,當你使用完該對象後可以通過調用這個destroy()方法來釋放該對象內部成員佔用的系統資源。雖然我們可以通過調用自己定義的destroy()方法釋放系統資源,但是還是建議你最好將對destroy()方法的調用放入當前類的finalize()方法體中,因為這樣做更保險,更安全。在類深度繼承的情況下,這種方法就顯得更為有效了,我們可以通過遞迴調用destroy的方法在子類被銷毀的時候釋放父類所佔用的資源,例如下面的代碼:
1.原始基類A
public class A {
Object a = null;
public A() {
a = new Object();
System.out.println("建立a對象");
}
protected void destroy() {
System.out.println("釋放a對象");
a = null;
// 釋放自身所佔用的資源
…
}
protected void finalize() throws java.lang.Throwable {
destroy();
// 遞迴調用超類中的finalize方法
super.finalize();
}
}
2.一級子類B
public class B extends A {
Object b = null;
public B() {
b = new Object();
System.out.println("建立b對象");
}
protected void destroy() {
b = null;
// 釋放自身所佔用的資源
System.out.println("釋放b對象");
super.destroy();
}
protected void finalize() throws java.lang.Throwable {
destroy();
// 遞迴調用超類中的finalize方法
super.finalize();
}
}
3.二級子類C
public class C extends B {
Object c = null;
public C() {
c = new Object();
System.out.println("建立c對象");
}
protected void destroy() {
c = null;
// 釋放自身所佔用的資源
System.out.println("釋放c對象");
super.destroy();
}
protected void finalize()throws java.lang.Throwable {
destroy();
// 遞迴調用超類中的finalize方法
super.finalize();
}
}
上面的三個類的繼承關係是非常明晰的:A->B->C,類A是原始基類(這是一種習慣叫法),類B繼承了類A,類C又繼承了類B。其實類A並不是真正意義上的原始基類,上面我們已經提到過Java語言中的原始基類是Object類,儘管我們並沒有顯式的聲明,但這已經是系統約定俗成的了。
為了簡單清楚地說明問題,我們在這三個類中分別聲明了3個方法,用來論證上面所講解的知識點,在類A的構造器中我們初始化了一個對象a,在destroy方法中通過a = null;釋放其自身所佔用的資源。並且在finalize方法中,我們調用了destroy方法用來釋放其自身所佔用的資源,然後調用其超類Object的finalize方法,這是我們以上所提到的“雙保險”的記憶體釋放方法;類B與類C的結構與類A極為相似,它們除了釋放自身所佔用的資源外,它們還在其對應的方法中調用其超類的destroy方法與finalize方法,用來釋放超類所佔用的資源。如在類B中調用其超類A的destroy方法與finalize方法與在類C中調用其超類B的destroy方法與finalize方法。但是類A與類B、類C有一點不同,那就是在其destroy方法中沒有super.destroy()語句,這是因為其超類Object並沒有destroy方法。下面看一下當我們調用初始化與銷毀類C時,會有什麼樣的情況發生。以下是調用完成這個過程的測試類別Test的原始碼:
public class Test {
c = null;
public Test () {
c = new C();
}
public static void main(String args[]) {
MyClass me = new MyClass();
me.destroy();
}
protected void destroy () {
if (c != null) {
c.destroy();
}else {
System.out.println("c對象已被釋放");
}
}
}
編譯執行Test.java:
> javac Test.java
> java Test
下面是這個程式的運行結果:
建立a對象
建立b對象
建立c對象
釋放c對象
釋放b對象
釋放a對象
我們注意到當在Test類中初始化類C的對象時,其構造器產生了遞迴調用,並且是由基類開始依次調用、初始化成員對象的,而當調用C類對象的destroy方法時系統同樣產生了遞迴調用,但調用的順序卻與初始化調用的順序完全相反,釋放資源的調用順序是由子類開始的,依次調用其超類的資源釋放方法destroy()。由此可見,我們在設計類時應儘可能地避免在類的預設構造器中建立、初始化大量的對象。一個原因是在執行個體化自身的情況下,造成較大的資源開銷;另一個原因是其子類在被執行個體化時,也同樣會帶來較大的系統資源開銷。因為即使我們沒有想調用父類的構造器建立大量無用的對象(至少有時候這些對象對我們是沒有意義的),但是系統會自動建立它們,而這些操作與過程對於我們來說是隱含的。為了防止上述情況的發生,造成不必要的記憶體資源浪費,我們應當盡量不在類的構造器中建立、初始化大量的對象或執行某種複雜、耗時的運算邏輯。