學(xué)習(xí)Java的同學(xué)注意了!!!
學(xué)習(xí)進(jìn)程中遇到甚么問(wèn)題或想獲得學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交換群,群號(hào)碼:183993990 我們1起學(xué)Java!
1、斟酌用靜態(tài)工廠方法代替構(gòu)造器:
構(gòu)造器是創(chuàng)建1個(gè)對(duì)象實(shí)例最基本也最通用的方法,大部份開(kāi)發(fā)者在使用某個(gè)class的時(shí)候,首先需要斟酌的就是如何構(gòu)造和初始化1個(gè)對(duì)象示例,而構(gòu)造的方式首先斟酌到的就是通過(guò)構(gòu)造函數(shù)來(lái)完成,因此在看javadoc中的文檔時(shí)首先關(guān)注的函數(shù)也是構(gòu)造器。但是在有些時(shí)候構(gòu)造器并不是我們唯1的選擇,通過(guò)反射也是可以輕松到達(dá)的。我們這里主要提到的方式是通過(guò)靜態(tài)類工廠的方式來(lái)創(chuàng)建class的實(shí)例,如:
1 public static Boolean valueOf(boolean b) { 2 return b ? Boolean.TRUE : Boolean.FALSE; 3 }
靜態(tài)工廠方法和構(gòu)造器不同有以下主要優(yōu)勢(shì):
1. 成心義的名稱。
在框架設(shè)計(jì)中,針對(duì)某些工具類通常會(huì)斟酌dummy對(duì)象或空對(duì)象以辨別該對(duì)象是不是已被初始化,如我曾在我的C++基礎(chǔ)庫(kù)中實(shí)現(xiàn)了String類型,見(jiàn)以下代碼:
1 void showExample() { 2 String strEmpty = String::empty(); 3 String strEmpty2 = ""; 4 String strData = String::prellocate(1024); 5 if (strEmpty.isEmpty()) { 6 //TODO: do something 7 } 8 } 9 static String String::emptyString; 10 String& String::empty() { 11 return emptyString; 12 } 13 14 bool String::isEmpty() { 15 if (this->_internal == &emptyString->_internal) 16 return true; 17 //TODO: do other justice to verify whether it is empty. 18 }
在上面的代碼中,提供了兩個(gè)靜態(tài)工廠方法empty和preallocate用于分別創(chuàng)建1個(gè)空對(duì)象和1個(gè)帶有指定分配空間的String對(duì)象。從使用方式來(lái)看,這些靜態(tài)方法確切提供了成心義的名稱,使用者很容易就能夠判斷出它們的作用和利用場(chǎng)景,而沒(méi)必要在1組重載的構(gòu)造器中去搜索每個(gè)構(gòu)造函數(shù)及其參數(shù)列表,以找出合適當(dāng)前場(chǎng)景的構(gòu)造函數(shù)。從效力方面來(lái)說(shuō),由于提供了唯1的靜態(tài)空對(duì)象,當(dāng)判讀對(duì)象實(shí)例是不是為空時(shí)(isEmpty),直接使用預(yù)制靜態(tài)空對(duì)象(emptyString)的地址與當(dāng)前對(duì)象進(jìn)行比較,如果是同1地址,便可確認(rèn)當(dāng)前實(shí)例為空對(duì)象了。對(duì)preallocate函數(shù),顧名思義,該函數(shù)預(yù)分配了指定大小的內(nèi)存空間,后面在使用該String實(shí)例時(shí),沒(méi)必要擔(dān)心賦值或追加的字符過(guò)量而致使頻繁的realloc等操作。
2. 沒(méi)必要在每次調(diào)用它們的時(shí)候創(chuàng)建1個(gè)新的對(duì)象。
還是基于上面的代碼實(shí)例,由于所有的空對(duì)象都同享同1個(gè)靜態(tài)空對(duì)象,這樣也節(jié)省了更多的內(nèi)存開(kāi)消,如果是strEmpty2方式構(gòu)造出的空對(duì)象,在履行比較等操作時(shí)會(huì)帶來(lái)更多的效力開(kāi)消。事實(shí)上,Java在String對(duì)象的實(shí)現(xiàn)中,使用了常量資源池也是基于了一樣的優(yōu)化策略。該優(yōu)勢(shì)一樣適用于單實(shí)例模式。
3. 可以返回原返回類型的任何子類型。
在Java Collections Framework的集合接口中,提供了大量的靜態(tài)方法返回集合接口類型的實(shí)現(xiàn)類型,如Collections.subList()、Collections.unmodifiableList()等。返回的接口是明確的,但是針對(duì)具體的實(shí)現(xiàn)類,函數(shù)的使用者其實(shí)不也無(wú)需知曉。這樣不但極大的減少了導(dǎo)出類的數(shù)量,而且在今后如果發(fā)現(xiàn)某個(gè)子類的實(shí)現(xiàn)效力較低或發(fā)現(xiàn)更好的數(shù)據(jù)結(jié)構(gòu)和算法來(lái)替換當(dāng)前實(shí)現(xiàn)子類時(shí),對(duì)集合接口的使用者來(lái)講,不會(huì)帶來(lái)任何的影響。本書(shū)在例子中提到EnumSet是通過(guò)靜態(tài)工廠方法返回對(duì)象實(shí)例的,沒(méi)有提供任何構(gòu)造函數(shù),其內(nèi)部在返回實(shí)現(xiàn)類時(shí)做了1個(gè)優(yōu)化,即如果枚舉的數(shù)量小于64,該工廠方法將返回1個(gè)經(jīng)過(guò)特殊優(yōu)化的實(shí)現(xiàn)類實(shí)例(RegularEnumSet),其內(nèi)部使用long(64bits在Java中)
中的不同位來(lái)表示不同的枚舉值。如果枚舉的數(shù)量大于64,將使用long的數(shù)組作為底層支持。但是這些內(nèi)部實(shí)現(xiàn)類的優(yōu)化對(duì)使用者來(lái)講是透明的。
4. 在創(chuàng)建參數(shù)化類型實(shí)例的時(shí)候,它們使代碼變得更加簡(jiǎn)潔。
Map
由于Java在構(gòu)造函數(shù)的調(diào)用中沒(méi)法進(jìn)行類型的推演,因此也就沒(méi)法通過(guò)構(gòu)造器的參數(shù)類型來(lái)實(shí)例化指定類型參數(shù)的實(shí)例化對(duì)象。但是通過(guò)靜態(tài)工廠方法則可以利用參數(shù)類型推演的優(yōu)勢(shì),避免了類型參數(shù)在1次聲明中被屢次重寫(xiě)所帶來(lái)的煩憂,見(jiàn)以下代碼:
public static
return new HashMap
}
Map
2、遇到多個(gè)構(gòu)造參數(shù)時(shí)要斟酌用構(gòu)建器(Builder模式):
如果1個(gè)class在構(gòu)造初始化的時(shí)候存在非常多的參數(shù),將會(huì)致使構(gòu)造函數(shù)或靜態(tài)工廠函數(shù)帶有大量的、類型相同的函數(shù)參數(shù),特別是當(dāng)1部份參數(shù)只是可選參數(shù)的時(shí)候,class的使用者不能不為這些可選參數(shù)也傳入缺省值,有的時(shí)候會(huì)發(fā)現(xiàn)使用者傳入的缺省值多是成心義的,而并不是class內(nèi)部實(shí)現(xiàn)所認(rèn)可的缺省值,比如某個(gè)整型可選參數(shù),通常使用者會(huì)傳入0,然后class內(nèi)部的實(shí)現(xiàn)恰恰認(rèn)為0是1種重要的狀態(tài),而該狀態(tài)其實(shí)不是該調(diào)用者關(guān)心的,但是該狀態(tài)卻間接致使其他狀態(tài)的改變,因此帶來(lái)了1些潛伏的狀態(tài)不1致問(wèn)題。與此同時(shí),過(guò)量的函數(shù)參數(shù)也給使用者的學(xué)習(xí)和使用帶來(lái)很多沒(méi)必要要的麻煩,我相信任何使用者都希望看到class的接口是簡(jiǎn)單易用、函數(shù)功能清晰可見(jiàn)的。在Effective
C++中針對(duì)接口的設(shè)計(jì)有這樣的1句話:"接口要完滿而最小化"。針對(duì)該類問(wèn)題通常會(huì)斟酌的方法是將所有的參數(shù)歸結(jié)到1個(gè)JavaBean對(duì)象中,實(shí)例化這個(gè)Bean對(duì)象,然后再將實(shí)例化的結(jié)果傳給這個(gè)class的構(gòu)造函數(shù),這類方法依然沒(méi)有避免缺省值的問(wèn)題。該條目推薦了Builder模式來(lái)創(chuàng)建這個(gè)帶有很多可選參數(shù)的實(shí)例對(duì)象。
1 class NutritionFacts { 2 private final int servingSize; 3 private final int servings; 4 private final int calories; 5 private final int fat; 6 private final int sodium; 7 private final int carbohydrate; 8 public static class Builder { 9 //對(duì)象的必選參數(shù) 10 private final int servingSize; 11 private final int servings; 12 //對(duì)象的可選參數(shù)的缺省值初始化 13 private int calories = 0; 14 private int fat = 0; 15 private int carbohydrate = 0; 16 private int sodium = 0; 17 //只用少數(shù)的必選參數(shù)作為構(gòu)造器的函數(shù)參數(shù) 18 public Builder(int servingSize,int servings) { 19 this.servingSize = servingSize; 20 this.servings = servings; 21 } 22 public Builder calories(int val) { 23 calories = val; 24 return this; 25 } 26 public Builder fat(int val) { 27 fat = val; 28 return this; 29 } 30 public Builder carbohydrate(int val) { 31 carbohydrate = val; 32 return this; 33 } 34 public Builder sodium(int val) { 35 sodium = val; 36 return this; 37 } 38 public NutritionFacts build() { 39 return new NutritionFacts(this); 40 } 41 } 42 private NutritionFacts(Builder builder) { 43 servingSize = builder.servingSize; 44 servings = builder.servings; 45 calories = builder.calories; 46 fat = builder.fat; 47 sodium = builder.sodium; 48 carbohydrate = builder.carbohydrate; 49 } 50 } 51 //使用方式 52 public static void main(String[] args) { 53 NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100) 54 .sodium(35).carbohydrate(27).build(); 55 System.out.println(cocaCola); 56 }
對(duì)Builder方式,可選參數(shù)的缺省值問(wèn)題也將不再困擾著所有的使用者。這類方式還帶來(lái)了1個(gè)間接的好處是,不可變對(duì)象的初始化和參數(shù)合法性的驗(yàn)證等工作在構(gòu)造函數(shù)中原子性的完成了。
3、用私有構(gòu)造器或枚舉類型強(qiáng)化Singleton屬性:
對(duì)單實(shí)例模式,相信很多開(kāi)發(fā)者其實(shí)不陌生,但是如何更好更安全的創(chuàng)建單實(shí)例對(duì)象還是需要1些斟酌和考慮的,在Java中主要的創(chuàng)建方式有以下3種,我們分別作出解釋和適當(dāng)?shù)谋容^。
1. 將構(gòu)造函數(shù)私有化,直接通過(guò)靜態(tài)公有的final域字段獲得單實(shí)例對(duì)象:
1 public class Elvis { 2 public static final Elvis INSTANCE = new Elvis(); 3 private Elivs() { ... } 4 public void leaveTheBuilding() { ... } 5 }
這樣的方式主要優(yōu)勢(shì)在于簡(jiǎn)潔高效,使用者很快就可以判定當(dāng)前類為單實(shí)例類,在調(diào)用時(shí)直接操作Elivs.INSTANCE便可,由于沒(méi)有函數(shù)的調(diào)用,因此效力也非常高效。但是事物是具有1定的雙面性的,這類設(shè)計(jì)方式在1個(gè)方向上走的過(guò)于極端了,因此他的缺點(diǎn)也會(huì)是非常明顯的。如果今后Elvis的使用代碼被遷移到多線程的利用環(huán)境下了,系統(tǒng)希望能夠做到每一個(gè)線程使用同1個(gè)Elvis實(shí)例,不同線程之間則使用不同的對(duì)象實(shí)例。那末這類創(chuàng)建方式將沒(méi)法實(shí)現(xiàn)該需求,因此需要修改接口和接口的調(diào)用者代碼,這樣就帶來(lái)了更高的修改本錢(qián)。
2. 通過(guò)公有域成員的方式返回單實(shí)例對(duì)象:
1 public class Elvis { 2 public static final Elvis INSTANCE = new Elvis(); 3 private Elivs() { ... } 4 public static Elvis getInstance() { return INSTANCE; } 5 public void leaveTheBuilding() { ... } 6 }
這類方法很好的彌補(bǔ)了第1種方式的缺點(diǎn),如果今后需要適應(yīng)多線程環(huán)境的對(duì)象創(chuàng)建邏輯,僅需要修改Elvis的getInstance()方法內(nèi)部便可,對(duì)用調(diào)用者而言則是不變的,這樣便極大的縮小了影響的范圍。至于效力問(wèn)題,當(dāng)今的JVM針對(duì)該種函數(shù)都做了很好的內(nèi)聯(lián)優(yōu)化,因此不會(huì)產(chǎn)生因函數(shù)頻繁調(diào)用而帶來(lái)的開(kāi)消。
3. 使用枚舉的方式(Java SE5):
1 public enum Elvis { 2 INSTANCE; 3 public void leaveTheBuilding() { ... } 4 }
就目前而言,這類方法在功能上和公有域方式相近,但是他更加簡(jiǎn)潔更加清晰,擴(kuò)大性更強(qiáng)也更加安全。
我在設(shè)計(jì)自己的表達(dá)式解析器時(shí),曾將所有的操作符設(shè)計(jì)為enum中不同的枚舉元素,同時(shí)提供了帶有參數(shù)的構(gòu)造函數(shù),傳入他們的優(yōu)先級(jí)、操作符名稱等信息。
4、通過(guò)私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力:
對(duì)有些工具類如java.lang.Math、java.util.Arrays等,其中只是包括了靜態(tài)方法和靜態(tài)域字段,因此對(duì)這樣的class實(shí)例化就顯得沒(méi)有任何意義了。但是在實(shí)際的使用中,如果不加任何特殊的處理,這樣的classes是可以像其他classes1樣被實(shí)例化的。這里介紹了1種方式,既將缺省構(gòu)造函數(shù)設(shè)置為private,這樣類的外部將沒(méi)法實(shí)例化該類,與此同時(shí),在這個(gè)私有的構(gòu)造函數(shù)的實(shí)現(xiàn)中直接拋出異常,從而也避免了類的內(nèi)部方法調(diào)用該構(gòu)造函數(shù)。
1 public class UtilityClass { 2 //Suppress default constructor for noninstantiability. 3 private UtilityClass() { 4 throw new AssertionError(); 5 } 6 }
這樣定義以后,該類將不會(huì)再被外部實(shí)例化了,否則會(huì)產(chǎn)生編譯毛病。但是這樣的定義帶來(lái)的最直接的負(fù)面影響是該類將不能再被子類化。
5、避免創(chuàng)建沒(méi)必要要的對(duì)象:
試比較以下兩行代碼在被屢次反復(fù)履行時(shí)的效力差異:
String s = new String("stringette");
String s = "stringette";
由于String被實(shí)現(xiàn)為不可變對(duì)象,JVM底層將其實(shí)現(xiàn)為常量池,既所有值等于"stringette" 的String對(duì)象實(shí)例同享同1對(duì)象地址,而且還可以保證,對(duì)所有在同1JVM中運(yùn)行的代碼,只要他們包括相同的字符串字面常量,該對(duì)象就會(huì)被重用。
我們繼續(xù)比較下面的例子,并測(cè)試他們?cè)谶\(yùn)行時(shí)的效力差異:
Boolean b = Boolean.valueOf("true");
Boolean b = new Boolean("true");
前者通過(guò)靜態(tài)工廠方法保證了每次返回的對(duì)象,如果他們都是true或false,那末他們將返回相同的對(duì)象。換句話說(shuō),valueOf將只會(huì)返回Boolean.TRUE或Boolean.FALSE兩個(gè)靜態(tài)域字段之1。而后面的Boolean構(gòu)造方式,每次都會(huì)構(gòu)造出1個(gè)新的Boolean實(shí)例對(duì)象。這樣在屢次調(diào)用后,第1種靜態(tài)工廠方法將會(huì)避免大量沒(méi)必要要的Boolean對(duì)象被創(chuàng)建,從而提高了程序的運(yùn)行效力,也下降了垃圾回收的負(fù)擔(dān)。
繼續(xù)比較下面的代碼:
1 public class Person { 2 private final Date birthDate; 3 //判斷該嬰兒是不是是在生育高峰期誕生的。 4 public boolean isBabyBoomer { 5 Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 6 c.set(1946,Calendar.JANUARY,1,0,0,0); 7 Date dstart = c.getTime(); 8 c.set(1965,Calendar.JANUARY,1,0,0,0); 9 Date dend = c.getTime(); 10 return birthDate.compareTo(dstart) >= 0 && birthDate.compareTo(dend) < 0; 11 } 12 } 13 14 public class Person { 15 private static final Date BOOM_START; 16 private static final Date BOOM_END; 17 18 static { 19 Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 20 c.set(1946,Calendar.JANUARY,1,0,0,0); 21 BOOM_START = c.getTime(); 22 c.set(1965,Calendar.JANUARY,1,0,0,0); 23 BOOM_END = c.getTime(); 24 } 25 public boolean isBabyBoomer() { 26 return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0; 27 } 28 }
改進(jìn)后的Person類只是在初始化的時(shí)候創(chuàng)建Calender、TimeZone和Date實(shí)例1次,而不是在每次調(diào)用isBabyBoomer方法時(shí)都創(chuàng)建1次他們。如果該方法會(huì)被頻繁調(diào)用,效力的提升將會(huì)極其顯著。
集合框架中的Map接口提供keySet方法,該方法每次都將返回底層原始Map對(duì)象鍵數(shù)據(jù)的視圖,而其實(shí)不會(huì)為該操作創(chuàng)建1個(gè)Set對(duì)象并填充底層Map所有鍵的對(duì)象拷貝。因此當(dāng)屢次調(diào)用該方法并返回不同的Set對(duì)象實(shí)例時(shí),事實(shí)上他們底層指向的將是同1段數(shù)據(jù)的援用。
在該條目中還提到了自動(dòng)裝箱行動(dòng)給程序運(yùn)行帶來(lái)的性能沖擊,如果可以通過(guò)原始類型完成的操作應(yīng)當(dāng)盡可能避免使用裝箱類型和他們之間的交互使用。見(jiàn)下例:
1 public static void main(String[] args) { 2 Long sum = 0L; 3 for (long i = 0; i < Integer.MAX_VALUE; ++i) { 4 sum += i; 5 } 6 System.out.println(sum); 7 }
本例中由于錯(cuò)把long sum定義成Long sum,其效力下降了近10倍,這其中的主要緣由便是該毛病致使了2的31次方個(gè)臨時(shí)Long對(duì)象被創(chuàng)建了。
6、消除過(guò)期的對(duì)象援用:
雖然Java不像C/C++那樣需要手工管理內(nèi)存資源,而是通過(guò)更加方便、更加智能的垃圾回收機(jī)制來(lái)幫助開(kāi)發(fā)者清算過(guò)期的資源。即使如此,內(nèi)存泄漏問(wèn)題依然會(huì)產(chǎn)生在你的程序中,只是和C/C++相比,Java中內(nèi)存泄漏更加藏匿,更加難以發(fā)現(xiàn),見(jiàn)以下代碼:
1 public class Stack { 2 private Object[] elements; 3 private int size = 0; 4 private static final int DEFAULT_INITIAL_CAPACITY = 16; 5 public Stack() { 6 elements = new Object[DEFAULT_INITIAL_CAPACITY]; 7 } 8 public void push(Object e) { 9 ensureCapacity(); 10 elements[size++] = e; 11 } 12 public Object pop() { 13 if (size == 0) 14 throw new EmptyStackException(); 15 return elements[--size]; 16 } 17 private void ensureCapacity() { 18 if (elements.length == size) 19 elements = Arrays.copys(elements,2*size+1); 20 } 21 }
以上示例代碼,在正常的使用中不會(huì)產(chǎn)生任何邏輯問(wèn)題,但是隨著程序運(yùn)行時(shí)間不斷加長(zhǎng),內(nèi)存泄漏釀成的副作用將會(huì)漸漸的顯現(xiàn)出來(lái),如磁盤(pán)頁(yè)交換、OutOfMemoryError等。那末內(nèi)存泄漏隱藏在程序中的甚么地方呢?當(dāng)我們調(diào)用pop方法是,該方法將返回當(dāng)前棧頂?shù)膃lements,同時(shí)將該棧的活動(dòng)區(qū)間(size)減1,但是此時(shí)被彈出的Object依然保持最少兩處援用,1個(gè)是返回的對(duì)象,另外一個(gè)則是該返回對(duì)象在elements數(shù)組中原有棧頂位置的援用。這樣即使外部對(duì)象在使用以后不再援用該Object,那末它依然不會(huì)被垃圾搜集器釋放,長(zhǎng)此以往致使了更多類似對(duì)象的內(nèi)存泄漏。修改方式以下:
1 public Object pop() { 2 if (size == 0) 3 throw new EmptyStackException(); 4 Object result = elements[--size]; 5 elements[size] = null; //手工將數(shù)組中的該對(duì)象置空 6 return result; 7 }
由于現(xiàn)有的Java垃圾搜集器已足夠只能和強(qiáng)大,因此沒(méi)有必要對(duì)所有不在需要的對(duì)象履行obj = null的顯示置空操作,這樣反而會(huì)給程序代碼的瀏覽帶來(lái)沒(méi)必要要的麻煩,該條目只是推薦在以下3中情形下需要斟酌資源手工處理問(wèn)題:
1) 類是自己管理內(nèi)存,如例子中的Stack類。
2) 使用對(duì)象緩存機(jī)制時(shí),需要斟酌被從緩存中換出的對(duì)象,或是長(zhǎng)時(shí)間不會(huì)被訪問(wèn)到的對(duì)象。
3) 事件監(jiān)聽(tīng)器和相干回調(diào)。用戶常常會(huì)在需要時(shí)顯示的注冊(cè),但是卻常常會(huì)忘記在不用的時(shí)候注銷這些回調(diào)接口實(shí)現(xiàn)類。
7、避免使用終結(jié)方法:
任何事情都存在其1定的雙面性或多面性,對(duì)C++的開(kāi)發(fā)者,內(nèi)存資源是需要手工分配和釋放的,而對(duì)Java和C#這類資源托管的開(kāi)發(fā)語(yǔ)言,更多的工作可以交給虛擬機(jī)的垃圾回收器來(lái)完成,由此C++程序得到了運(yùn)行效力,卻失去了安全。在Java的實(shí)際開(kāi)發(fā)中,并不是所有的資源都是可以被垃圾回收器自動(dòng)釋放的,如FileInputStream、Graphic2D等class中使用的底層操作系統(tǒng)資源句柄,其實(shí)不會(huì)隨著對(duì)象實(shí)例被GC回收而被釋放,但是這些資源對(duì)全部操作系統(tǒng)而言,都是非常重要的稀缺資源,更多的資源句柄泄漏將會(huì)致使全部操作系統(tǒng)及其運(yùn)行的各種服務(wù)程序的運(yùn)行效力直線降落。那末如何保證系統(tǒng)資源不會(huì)被泄漏了?在C++中,由于其資源完全交由開(kāi)發(fā)者自行管理,因此在決定資源什么時(shí)候釋放的問(wèn)題上有著很優(yōu)雅的支持,C++中的析構(gòu)函數(shù)可以說(shuō)是完成這1工作的天然候選者。任何在棧上聲明的C++對(duì)象,當(dāng)棧退出或當(dāng)前對(duì)象離開(kāi)其作用域時(shí),該對(duì)象實(shí)例的析構(gòu)函數(shù)都會(huì)被自動(dòng)調(diào)用,因此當(dāng)函數(shù)中有任何異常(Exception)產(chǎn)生時(shí),在棧被燒毀之前,所有棧對(duì)象的析構(gòu)函數(shù)均會(huì)被自動(dòng)調(diào)用。但是對(duì)Java的開(kāi)發(fā)者而言,從語(yǔ)言本身視角來(lái)看,Java本身并未提供析構(gòu)函數(shù)這樣的機(jī)制,固然這也是和其資源被JVM托管有1定關(guān)系的。
在Java中完成這樣的工作主要是依托try-finally機(jī)制來(lái)協(xié)助完成的。但是Java中還提供了另外1種被稱為finalizer的機(jī)制,使用者僅僅需要重載Object對(duì)象提供的finalize方法,這樣當(dāng)JVM的在進(jìn)行垃圾回收時(shí),就能夠自動(dòng)調(diào)用該方法。但是由于對(duì)象什么時(shí)候被垃圾搜集的不肯定性,和finalizer給GC帶來(lái)的性能上的影響,因此其實(shí)不推薦使用者依托該方法來(lái)到達(dá)關(guān)鍵資源釋放的目的。比如,有數(shù)千個(gè)圖形句柄都在等待被終結(jié)和回收,惋惜的是履行終結(jié)方法的線程優(yōu)先級(jí)要低于普通的工作者線程,這樣就會(huì)有大量的圖形句柄資源停留在finalizer的隊(duì)列中而不能被及時(shí)的釋放,終究致使了系統(tǒng)運(yùn)行效力的降落,乃至還會(huì)引發(fā)JVM報(bào)出OutOfMemoryError的毛病。
Java的語(yǔ)言規(guī)范中并沒(méi)有保證該方法會(huì)被及時(shí)的履行,乃至都沒(méi)有保證1定會(huì)被履行。即使開(kāi)發(fā)者在code中手工調(diào)用了System.gc和System.runFinalization這兩個(gè)方法,這僅僅是提高了finalizer被履行的概率而已。還有1點(diǎn)需要注意的是,被重載的finalize()方法中如果拋出異常,其棧幀軌跡是不會(huì)被打印出來(lái)的。在Java中被推薦的資源釋放方法為,提供顯式的具有良好命名的接口方法,如FileInputStream.close()和Graphic2D.dispose()等。然后使用者在finally區(qū)塊中調(diào)用該方法,見(jiàn)以下代碼:
1 public void test() { 2 FileInputStream fin = null; 3 try { 4 fin = new FileInputStream(filename); 5 //do something. 6 } finally { 7 fin.close(); 8 } 9 }
那末在實(shí)際的開(kāi)發(fā)中,利用finalizer又能給我們帶來(lái)甚么樣的幫助呢?見(jiàn)下例:
1 public class FinalizeTest { 2 //@Override 3 protected void finalize() throws Throwable { 4 try { 5 //在調(diào)試進(jìn)程中通過(guò)該方法,打印對(duì)象在被搜集前的各種狀態(tài), 6 //如判斷是不是仍有資源未被釋放,或是不是有狀態(tài)不1致的現(xiàn)象存在。 7 //推薦將該finalize方法設(shè)計(jì)成僅在debug狀態(tài)下可用,而在release 8 //下該方法其實(shí)不存在,以免其對(duì)運(yùn)行時(shí)效力的影響。 9 System.out.println("The current status: " + _myStatus); 10 } finally { 11 //在finally中對(duì)超類finalize方法的調(diào)用是必須的,這樣可以保證全部class繼承 12 //體系中的finalize鏈都被履行。 13 super.finalize(); 14 } 15 } 16 }