天天看點

JAVA高效程式設計指南

JAVA高效程式設計指南

1 建立和銷毀對象

1.1 考慮用靜态工廠方法替代構造函數

如:

public static Boolean valueOf(boolean b){

return (b?Boolean.TRUE:Boolean.False);

}

優勢:

它們具有自己的名字

不需要在每次調用時都去建立一個新的對象

可以傳回任何子類型的對象

1.2 使用私有構造函數強化singleton屬性

singleton類就是一種隻能被執行個體化一次的簡單類。這種類型典型地被用來表示那些本性上具有唯一特性的系統元件,如:JNDI的name查找器。

具體例子參見:com.zte.resmaster.helpers.NamingHelper

1.3 用私有構造函數強化不可執行個體化能力

有些工具類不希望被執行個體化:對它進行執行個體化沒有意義。如:java.lang.Math。然而在類中缺少顯式構造函數的時候,編譯器會自動地提供一個公共的無參數的預設構造函數。在類中包含顯式的私有類型構造函數來實作類的不可執行個體化特性。因為構造函數是私有的,是以它在類的外部不可通路。如果構造函數不會被類自身從内部調用,即能保證類永遠不會被執行個體化。

1.4 避免建立重複對象

String s= new String("guojun");//永遠不要這麼幹!

Strong s = "guojun";//可以接受的改進

1.5 消除對過期對象的引用

一旦一個對象的引用被不小心地保留,不僅這個對象被排除在垃圾回收之外。而且所有被這個對象引用的對象也不會被垃圾收集,并順延下去。

一旦對象過期,清除資源以及對它們的引用。尤其自己的程式管理某些對象時,尤其如此。如類中使用對象數組,HashMap 等等。

2 對象定義

2.1 重載equals時要遵守的約定

滿足下面的條件就不要重載:

 每個類執行個體本質上是唯一的

 不關心類是否提供了“邏輯意義的等同“測試

 超類已經重載了equals,而超類繼承的行為适合該類

 類是私有的或是或是包内私有的,而且可以确定它的equals方法永遠不會被調用。

當類有邏輯上的等同意義而不僅僅是對象意義上的等同,而且超類沒有重載equals方法以實作期望的行為,這時需要重載。這種情形通常是數值類型的類(Value Object)。這時,equals的重載不僅對滿足程式員的需要是必須的,它也能使類執行個體通過可預知的,期望的行為來做map關鍵字或set元素,以及Collection中的通路。

2.2 重載equals時永遠重載hashCode

在EntityBean中的關鍵字對象,必須這麼做,否則程式會出現編譯錯誤。

2.3 永遠重載toString()

推薦所有的子類重載這個方法,當對象傳遞給println方法/串的連接配接操作(+)時,toString()方法會自動調用。附加效果:有利于調試。

如:定義一個人井對象,在toString()中傳回人井名稱或是編碼,那麼當調用參數或是傳回值為人井對象時,你無需做其他編碼,就能在Jbuilder中看到EJB調用時的人井的toString()産生的資訊。

2.4 謹慎重載clone

3 類和接口

3.1 最小化類和成員的可通路能力

隐藏子產品的内部資料和實作細節,僅僅把該暴露的部分暴露給調用者。這也是OO三概念之一:封裝。

如果把某些不應該暴露的方法暴露給調用者,一旦某個調用者使用了它,那麼為了保證程式相容,你就得繼續提供對該方法的支援。

通路性:

 私有(private) 僅在類内部可通路

 預設的(default) 對包内的所有類可通路(注意:沒有default的關鍵字,當沒有指明任何修飾符時,就是這種類型)

 受保護的(protected) 該類的子類和包内所有類可通路

 公共的(public) 通路不受限制

子類重載父類的方法,子類中方法的通路能力不能低于父類中該方法的通路能力;否則編譯器産生編譯錯誤。

3.2 組合優于繼承

當确實需要繼承時才采用繼承,否則使用類組合來完成。

3.3 接口優于抽象類

兩種機制最明顯的差別是抽象類容許包含某些方法的實作,而接口不行。

如:com.zte.resmaster.helpers.AbstractDAOImpl,就是抽象類,它具有得到資料庫連接配接和清除資料庫資源的方法,但是怎麼實作具體的DAO操作必須是其子類實作。

如果設計成抽象類,一旦子類要求繼承其他類時就沒有辦法實作,因為Java中不能多重繼承(extends),但是可以多重實作(implements)。換句話說就是接口是定義混合類型的理想選擇。

接口允許非層次類型架構的構造。

接口通過封裝類方式,能夠獲得安全、強大的功能。

當然抽象類有另一個優勢:改進抽象類比改進接口更加容易。一旦某個方法的實作發生了變化,僅僅需要更改一個地方;當新加一個方法時,所有子類都具有了該方法。

3.4 靜态成員優于非靜态的

因為不需要建構類的執行個體。例如,QuyuDAOFactory的getDAO()方法。

3.5 盡量使用内部類

如果該類僅僅在某個調用者使用,那麼把它設計成調用者的内部類。具體的例子,參見:com.zte.resmaster.system.rescomp.dao.RescompDAOOracle。不會暴露該内部類,避免濫用而導緻的維護問題。

4 結構的替代

4.1 用類替代結構

使用private修飾屬性,使用get和set來擷取和變更屬性。值對象模型可以看成這樣的例子。

4.2 用類層次替代聯合

内部類的使用,或是類的組合。

4.3 用類替代枚舉

5 方法

5.1 認真設計方法簽名

選好名稱

避免長參數清單,類型相同的長參數清單尤其有害。需要很多參數時,用一個class包裝起來,就像結構(Struct)做的一樣。

在參數類型的使用上,接口優于類。顯然使用Collection作為參數比使用ArrayList要好。因為Collection的參數可以使用ArrayList,LinkedList以及Vector來指派調用;而ArrayList卻必須是ArrayList來調用。

5.2 謹慎使用過載(overload)

安全保守的政策:永遠不要寫兩個具有相同參數數目的過載方法。

5.3 傳回0長度的數組而不是null

對于Collection的情況尤其如此。

6 通用程式設計

6.1 最小化局部變量的作用域

最小化局部變量最有效的方法是在它第一次使用時聲明。

幾乎每個局部變量聲明都應該包含一個初始值,否則編譯也通不過。

方法小而集中,每個方法僅僅處理一種活動。

6.2 了解和使用庫

Java提供了強大而複雜的類庫,在完成某個常用功能之前,看看Java類庫中是否已經存在類似功能的類庫。一方面減少工作量,一方面使得自己的程式更加簡潔高效。

例如:向量(Collection,Set,List…)中字元串的排序:Collections.sort(v);

忽略大小寫的排序:Collections.sort(v,String.CASE_INSENSITIVE_ORDER)

還有許多實用類庫。

6.3 精确資料計算時,不要使用float或是double類型

使用int或是long進行精确資料計算,自己管理小數位。

當數目不超過9位時,使用int類型;當數目不超過18位時,使用long;如果超過了18位,使用BigDecimal(注意它是對象,不是基本資料類型)。

6.4 盡量避免使用串

例如:從JSP或是其他用戶端得到某些資訊,其中包含int,Date,String類型,盡早把它們轉化為自己合适的資料類型。

6.5 了解字元串“+”的性能

當字元串不可變化時,使用String類型;當可變時使用StringBuffer類型。

當需要字元串+時,使用StringBuffer。這些例子大家可以在“動态對象管理”的DAO實作類中找到。

String s = “aaa” + “bbb”;

上面的語句實際上建立了三個String對象,性能受損。

StringBuffer s= new StringBuffer();

s.append(“aaa”);

s.append(“bbb”);

上面僅僅建立一個對象。

6.6 通過接口通路對象

如果存在合适的接口類型,那麼參數、傳回值、變量和域應該用接口類型聲明。

如果沒有合适的接口,那麼通過類而不是接口通路對象,是完全合适的。

6.7 接口優于反射

失去在編譯期間進行類型檢查提供的好處。

代碼笨拙而冗長。

性能損失。反射比正常的方法調用慢40倍左右(JDK1.3)。

有的時候,付出代價是值得的,因為能夠得到更多的益處;一般而言,可以通過反射建立一個執行個體,然後通過他們的接口或是超類正常通路他們。

例如:DAO的Factory設計模式中的通過配置實作資料庫實作類的平滑移植。

6.8 謹慎地使用固有方法

Java Native Interface。不要因為性能這麼做,在JDK1.3中Java地速度已經大幅提高,在JDK1.4提高得更多,幾乎已經達到了C++的水準。如果你使用JDK1.3以下的版本,你會發現很多方法都是C或是C++實作的。如:BigInteger。但是在JDK1.3中它已經通過Java重新改寫了。

當然,你可以使用JNI,但是記住,必須使用有嚴格保證的代碼,如:SUN送出的代碼。因為不小心的C/C++的指針操作,會引起JVM崩潰。

如:你可以使用SUN提供的操縱COM(序列槽/并口)的代碼;非常好用,提供Windows和Solaris版本的支援。

6.9 實作功能後在想如何優化是一個比較好的政策。

6.10 遵循普遍接受的命名規則,尤其是團隊内的規則。

7 異常

設計良好的API不應迫使使用者使用異常做正常的控制流程。

對可恢複的情況使用已檢查異常,對程式錯使用運作時異常。例如:某個對象在資料庫中已經存在,就屬于可恢複的異常——不能再加入那個對象了,如果使用關鍵字限制的話。

資料庫連接配接錯,就屬于不可恢複的異常,你的程式不可能對它做任何處理,隻能捕捉它。

使失敗原子化。

為你的系統定義一個統一的異常,系統中的其他異常都繼承該異常。

8 串行化

為了分布式應用中能夠傳遞對象,你可能使用串行化。

一般而言,你不需要做其他事情,除了實作java.io.Serializable接口。如果要通過RMI-IIOP遠端傳遞,那麼必須實作該接口。否則,會報告解析錯。

每一個可串行化的類都要與一個唯一的辨別符關聯,如果沒有顯示聲明一個private static final long 的serialVersionUID,那麼系統會通過一個複雜的計算過程為類自動生成一個唯一的辨別符。

任何對類名、實作的接口名稱、公共的或是受保護的成員或是函數名稱的改變都将自動産生一個新的UID。

新類和舊類的UID不一緻時,你的分布式應用就會失敗。

可以采用“serialver.exe -show”能夠為類産生一個UID。

9 避免記憶體洩漏

由于Java中有記憶體垃圾收集的機制,并且沒有指針類型,開發者就可以從C/C++的記憶體噩夢中解脫出來。正因為如此,很多開發者就不再關注記憶體的使用問題。小心,這裡存在陷阱。

請注意,Java的這個機制是記憶體垃圾收集而非記憶體收集!是以,你需要把使用的對象變成JVM可以識别的垃圾。一般而言,在方法中聲明使用的變量在方法外面時就會成為垃圾。但是有些卻不是,例如:

private HashMap dialogMap = new HashMap();

...

{

JDialog dialog = new JDialog(“XXX”);

...

dialogMap.put(dialog.getTitle(),dialog);

}

...

如果你在整個代碼期間都沒有調用如下語句:

Object ref =dialogMap.remove(key);

...(釋放資源,如:((JDialog)ref).dispose())

ref = null;

或者

dialogMap.clear()(有時,僅僅這個語句還不行)

那麼你的代碼就存在記憶體洩漏問題!

幾乎所有存儲對象的結構都要引起注意,看看是否有資源沒有釋放,沒有成為垃圾而無法回收。下面的對象要嚴格釋放:

java.sql.Connection;

java.sql.Statement;

java.sql.PreparedStatement

java.sql.CallableStatement

java.sql.ResultSet

這些對象如果不釋放其資源,不僅僅是記憶體問題;還将引起資料庫問題。如果不釋放Connection,那麼很快就用盡連接配接或是資料庫巨慢(資料庫連接配接數目還受到Licence的限制)。如果不釋放後面的對象資源,那麼很快就會用盡資料庫遊标,因為每打開一次後面的資源就要使用一個遊标,即使語句中沒有使用遊标(其實每個DDL語句都使用一個預設遊标)。而資料庫的遊标一般是有限制的,Oracle8.1.6中預設為100,Oracle8.1.7中預設為300。

千萬注意!

可能在一段時間内永遠不會碰到記憶體不足的問題,是不是就不用警惕上面提到的内容呢?

不!

JVM中垃圾記憶體的收集還受到記憶體容量的影響。當還有可用記憶體時,常常不會主動去垃圾收集。這就是為什麼常常沒有碰到記憶體不足的問題,因為你機器的記憶體足夠大(256M)。

為了Java程式有序的運作,你可以為它設定一個最大最小記憶體使用量。就像Weblogic中的一樣,如:

-hotspot -ms64m -mx64m。

這樣有利于更好的利用垃圾收集機制。

10 結束語

這裡所說的是一些技巧,這些技巧在大多數場合适用,它們不是真理。您在開發的過程發現自己會了解更多,當然希望您對該文檔提出批評意見或是補充。

希望這個文檔對大家有所幫助,謝謝大家。

繼續閱讀