Effective Java一書筆記

對象的創(chuàng)建與銷毀

Item 1:

使用static工廠方法,而不是構(gòu)造函數(shù)創(chuàng)建對象:
僅僅是創(chuàng)建對象的方法,并非Factory Pattern優(yōu)點(diǎn)命名、接口理解更高效,通過工廠方法的函數(shù)名,而不是參數(shù)列表來表達(dá)其語義
Instance control,并非每次調(diào)用都會創(chuàng)建新對象,可以使用預(yù)先創(chuàng)建好的對象,或者做對象緩存;便于實(shí)現(xiàn)單例;或不可實(shí)例化的類;對于immutable的對象來說,使得用==判等符合語義,且更高效;
工廠方法 能夠返回任何返回類型的子類對象,甚至是私有實(shí)現(xiàn);使得開發(fā)模塊之間通過接口耦合,降低耦合度;而接口的實(shí)現(xiàn)也將更加靈活;接口不能有static方法,通常做法是為其再創(chuàng)建一個(gè)工廠方法類,如CollectionCollections;
缺點(diǎn)僅有static工廠方法,沒有public/protected構(gòu)造函數(shù)的類將無法被繼承;見仁見智,這一方面也迫使開發(fā)者傾向于組合而非繼承;Javadoc中不能和其他static方法區(qū)分開,沒有構(gòu)造函數(shù)的集中顯示優(yōu)點(diǎn);但可以通過公約的命名規(guī)則來改善;

小結(jié)static工廠方法和public構(gòu)造函數(shù)均有其優(yōu)缺點(diǎn),在編碼過程中,可以先考慮一下工廠方法是否合適,再進(jìn)行選擇。

Item 2:

使用當(dāng)構(gòu)造函數(shù)的參數(shù)較多,尤其是其中還有部分是可選參數(shù)時(shí),使用Builder模式以往的方法Telescoping constructor:針對可選參數(shù),從0個(gè)到最多個(gè),依次編寫一個(gè)構(gòu)造函數(shù),它們按照參數(shù)數(shù)量由少到多逐層調(diào)用,最終調(diào)用到完整參數(shù)的構(gòu)造函數(shù);代碼冗余,有時(shí)還得傳遞無意義參數(shù),而且容易導(dǎo)致使用過程中出隱蔽的bug;
JavaBeans Pattern:靈活,但是缺乏安全性,有狀態(tài)不一致問題,線程安全問題;

Builder Pattern代碼靈活簡潔;具備安全性;
immutable
參數(shù)檢查:最好放在要build的對象的構(gòu)造函數(shù)中,而非builder的構(gòu)建過程中
支持多個(gè)field以varargs的方式設(shè)置(每個(gè)函數(shù)只能有一個(gè)varargs)
一個(gè)builder可以build多個(gè)對象
Builder結(jié)合泛型,實(shí)現(xiàn)Abstract Factory Pattern
傳統(tǒng)的抽象工廠模式,是用Class類實(shí)現(xiàn)的,然而其有缺點(diǎn):newInstance調(diào)用總是去調(diào)用無參數(shù)構(gòu)造函數(shù),不能保證存在;newInstance方法會拋出所有無參數(shù)構(gòu)造函數(shù)中的異常,而且不會被編譯期的異常檢查機(jī)制覆蓋;可能會導(dǎo)致運(yùn)行時(shí)異常,而非編譯期錯(cuò)誤;

小結(jié)Builder模式在簡單地類(參數(shù)較少,例如4個(gè)以下)中,優(yōu)勢并不明顯,但是需要予以考慮,尤其是當(dāng)參數(shù)可能會變多時(shí),有可選參數(shù)時(shí)更是如此。

Item 3:

單例模式!
不管以哪種形式實(shí)現(xiàn)單例模式,它們的核心原理都是將構(gòu)造函數(shù)私有化,并且通過靜態(tài)方法獲取一個(gè)唯一的實(shí)例,在這個(gè)獲取的過程中你必須保證線程安全、反序列化導(dǎo)致重新生成實(shí)例對象等問題,該模式簡單,但使用率較高。

<code>
private static volatile RestAdapter sRestAdapter = null;
public static RestAdapter provideRestAdapter() {
if (sRestAdapter == null) {
synchronized (RestProvider.class) {
if (sRestAdapter == null) {
sRestAdapter = new RestAdapter();
}
}
}
return sRestAdapter;
}
</code>

DCL可能會失效,因?yàn)橹噶钪嘏趴赡軐?dǎo)致同步解除后,對象初始化不完全就被其他線程獲??;使用volatile關(guān)鍵字修飾對象,或者使用static SingletonHolder來避免該問題(后者JLS推薦);

  • class的static代碼:一個(gè)類只有在被使用時(shí)才會初始化,而類初始化過程是非并行的,這些都由JLS能保證
  • 用enum實(shí)現(xiàn)單例
    還存在反射安全性問題:利用反射,可以訪問私有方法,可通過加一個(gè)控制變量,該變量在getInstance函數(shù)中設(shè)置,如果不是從getInstance調(diào)用構(gòu)造函數(shù),則拋出異常;
Item 4:

將構(gòu)造函數(shù)私有化,使得不能從類外創(chuàng)建實(shí)例,同時(shí)也能禁止類被繼承util類可能不希望被實(shí)例化,有其需求

Item 5:

避免創(chuàng)建不必要的對象提高性能:創(chuàng)建對象需要時(shí)間、空間,“重量級”對象尤甚;immutable的對象也應(yīng)該避免重復(fù)創(chuàng)建,例如String;
避免auto-boxing
但是因此而故意不創(chuàng)建必要的對象是錯(cuò)誤的,使用object pool通常也是沒必要的
lazy initialize也不是特別必要,除非使用場景很少且很重量級
Map#keySet方法,每次調(diào)用返回的是同一個(gè)Set對象,如果修改了返回的set,其他使用的代碼可能會產(chǎn)生bug
需要defensive copying的時(shí)候,如果沒有創(chuàng)建一個(gè)新對象,將導(dǎo)致很隱藏的Bug

Item 6:

不再使用的對象一定要解除引用,避免memory leak
例如,用數(shù)組實(shí)現(xiàn)一個(gè)棧,pop的時(shí)候,如果僅僅是移動下標(biāo),沒有把pop出棧的數(shù)組位置引用解除,將發(fā)生內(nèi)存泄漏
程序發(fā)生錯(cuò)誤之后,應(yīng)該盡快把錯(cuò)誤拋出,而不是以錯(cuò)誤的狀態(tài)繼續(xù)運(yùn)行,否則可能導(dǎo)致更大的問題
通過把變量(引用)置為null不是最好的實(shí)現(xiàn)方式,只有在極端情況下才需要這樣;好的辦法是通過作用域來使得變量的引用過期,所以盡量縮小變量的作用域是很好的實(shí)踐;

注意,在Dalvik虛擬機(jī)中,存在一個(gè)細(xì)微的bug,可能會導(dǎo)致內(nèi)存泄漏,詳見
當(dāng)一個(gè)類管理了一塊內(nèi)存,用于保存其他對象(數(shù)據(jù))時(shí),例如用數(shù)組實(shí)現(xiàn)的棧,底層通過一個(gè)數(shù)組來管理數(shù)據(jù),但是數(shù)組的大小不等于有效數(shù)據(jù)的大小,GC器卻并不知道這件事,所以這時(shí)候,需要對其管理的數(shù)據(jù)對象進(jìn)行null解引用
當(dāng)一個(gè)類管理了一塊內(nèi)存,用于保存其他對象(數(shù)據(jù))時(shí),程序員應(yīng)該保持高度警惕,避免出現(xiàn)內(nèi)存泄漏,一旦數(shù)據(jù)無效之后,需要立即解除引用
實(shí)現(xiàn)緩存的時(shí)候也很容易導(dǎo)致內(nèi)存泄漏,放進(jìn)緩存的對象一定要有換出機(jī)制,或者通過弱引用來進(jìn)行引用listnercallback也有可能導(dǎo)致內(nèi)存泄漏,最好使用弱引用來進(jìn)行引用,使得其可以被GC

Item 7:

不要使用finalize方法 : finalize方法不同于C++的析構(gòu)函數(shù),不是用來釋放資源的好地方
finalize方法執(zhí)行并不及時(shí),其執(zhí)行線程優(yōu)先級很低,而當(dāng)對象unreachable之后,需要執(zhí)行finalize方法之后才能釋放,所以會導(dǎo)致對象生存周期變長,甚至根本不會釋放
finalize方法的執(zhí)行并不保證執(zhí)行成功/完成
使用finalize時(shí),性能會嚴(yán)重下降
finalize存在的意義充當(dāng)“safety net”的角色,避免對象的使用者忘記調(diào)用顯式termination方法,盡管finalize方法的執(zhí)行時(shí)間沒有保證,但是晚釋放資源好過不釋放資源;此處輸出log警告有利于排查bug
用于釋放native peer,但是當(dāng)native peer持有必須要釋放的資源時(shí),應(yīng)該定義顯式termination方法

子類finalize方法并不會自動調(diào)用父類finalize方法(和構(gòu)造函數(shù)不同),為了避免子類不手動調(diào)用父類的finalize方法導(dǎo)致父類的資源未被釋放,當(dāng)需要使用finalize時(shí),使用finalizer guardian比較好:定義一個(gè)私有的匿名Object子類對象,重寫其finalize方法,在其中進(jìn)行父類要做的工作
因?yàn)楫?dāng)父類對象被回收時(shí),finalizer guardian也會被回收,它的finalize方法就一定會被觸發(fā)

Object的方法
盡管Object不是抽象類,但是其定義的非final方法設(shè)計(jì)的時(shí)候都是希望被重寫的,finalize除外。

Item 8:

當(dāng)重寫equals方法時(shí),遵循其語義能不重寫equals時(shí)就不要重寫當(dāng)對象表達(dá)的不是值,而是可變的狀態(tài)時(shí)
對象不需要使用判等時(shí)
父類已重寫,且滿足子類語義

當(dāng)需要判等,且繼承實(shí)現(xiàn)無法滿足語義時(shí),需要重寫(通常是“value class”,或immutable對象
當(dāng)用作map的key時(shí)
重寫equals時(shí)需要遵循的語義Reflexive(自反性):

x.equals(x)必須返回true(x不為null)
Symmetric(對稱性): x.equals(y) == y.equals(x)
Transitive(傳遞性): x.equals(y) && y.equals(z) ==> x.equals(z)
Consistent(一致性): 當(dāng)對象未發(fā)生改變時(shí),多次調(diào)用應(yīng)該返回同一結(jié)果
x.equals(null)必須返回false

實(shí)現(xiàn)建議先用==檢查是否引用同一對象,提高性能
instanceof再檢查是否同一類型
再強(qiáng)制轉(zhuǎn)換為正確的類型
再對各個(gè)域進(jìn)行equals檢查,遵循同樣的規(guī)則
確認(rèn)其語義正確,編寫測例
重寫equals時(shí),同時(shí)也重寫hashCode
!重寫equals方法,傳入的參數(shù)是Object

Item 9:

重寫equals時(shí),也要重寫hashCode函數(shù)避免在基于hash的集合中使用時(shí)出錯(cuò)
語義一致性
當(dāng)兩個(gè)對象equals返回true時(shí),hashCode方法的返回值也要相同

hashCode的計(jì)算方式要求:
equals的兩個(gè)對象hashCode一樣,但是不equals的對象hashCode不一樣
取一個(gè)素?cái)?shù),例如17,result = 17

對每一個(gè)關(guān)心的field(在equals中參與判斷的field),記為f,將其轉(zhuǎn)換為一個(gè)int,記為
cboolean: f ? 1 : 0
byte/char/short/int: (int) f
long: (int) (f ^ (f >> 32))
float: Float.floatToIntBits(f)
double: Double.doubleToLongBits(f),再按照long處理
Object: f == null ? 0 : f.hashCode()
array: 先計(jì)算每個(gè)元素的hashCode,再按照int處理

對每個(gè)field計(jì)算的c,result = 31 * result + c
返回result
編寫測例

計(jì)算hashCode時(shí),不重要的field(未參與equals判斷)不要參與計(jì)算

Item 10:

重寫toString()方法增加可讀性,簡潔、可讀、具有信息量

Item 11:

慎重重寫 clone方法Cloneable接口是一個(gè)mixin interface,用于表明一個(gè)對象可以被clone
Contractx.clone() != x
x.clone().getClass() == x.getClass():要求太弱,當(dāng)一個(gè)非final類重寫clone方法的時(shí)候,創(chuàng)建的對象一定要通過super.clone()來獲得,所有父類都遵循同樣的原則,如此最終通過Object.clone()創(chuàng)建對象,能保證創(chuàng)建的是正確的類實(shí)例。而這一點(diǎn)很難保證。
x.clone().equals(x)
不調(diào)用構(gòu)造函數(shù):要求太強(qiáng),一般都會在clone函數(shù)里面調(diào)用

對于成員變量都是primitive type的類,直接調(diào)用super.clone(),然后cast為自己的類型即可(重寫時(shí)允許返回被重寫類返回類型的子類,便于使用方,不必每次cast)
成員變量包含對象(包括primitive type數(shù)組),可以通過遞歸調(diào)用成員的clone方法并賦值來實(shí)現(xiàn)
然而上述方式違背了final的使用協(xié)議,final成員不允許再次賦值,然而clone方法里面必須要對其賦值,則無法使用final保證不可變性了
遞歸調(diào)用成員的clone方法也會存在性能問題,對HashTable遞歸調(diào)用深拷貝也可能導(dǎo)致StackOverFlow(可以通過遍歷添加來避免)
優(yōu)雅的方式是通過super.clone()創(chuàng)建對象,然后為成員變量設(shè)置相同的值,而不是簡單地遞歸調(diào)用成員的clone方法
和構(gòu)造函數(shù)一樣,在clone的過程中,不能調(diào)用non final的方法,如果調(diào)用虛函數(shù),那么該函數(shù)會優(yōu)先執(zhí)行,而此時(shí)被clone的對象狀態(tài)還未完成clone/construct,會導(dǎo)致corruption。因此上一條中提及的“設(shè)置相同的值”所調(diào)用的方法,要是final或者private。
重載類的clone方法可以省略異常表的定義,如果重寫時(shí)把可見性改為public,則應(yīng)該省略,便于使用;如果設(shè)計(jì)為應(yīng)該被繼承,則應(yīng)該重寫得和Object的一樣,且不應(yīng)該實(shí)現(xiàn)Cloneable接口;多線程問題也需要考慮;
要實(shí)現(xiàn)clone方法的類,都應(yīng)該實(shí)現(xiàn)Cloneable接口,同時(shí)把clone方法可見性設(shè)為public,返回類型為自己,應(yīng)該調(diào)用super.clone()來創(chuàng)建對象,然后手動設(shè)置每個(gè)域的值
clone方法太過復(fù)雜,如果不實(shí)現(xiàn)Cloneable接口,也可以通過別的方式實(shí)現(xiàn)copy功能,或者不提供copy功能,immutable提供copy功能是無意義的
提供拷貝構(gòu)造函數(shù),或者拷貝工廠方法,而且此種方法更加推薦,但也有其不足
設(shè)計(jì)用來被繼承的類時(shí),如果不實(shí)現(xiàn)一個(gè)正確高效的clone重寫,那么其子類也將無法實(shí)現(xiàn)正確高效的clone功能

Item 12: 當(dāng)對象自然有序時(shí),實(shí)現(xiàn)Comparable接口
實(shí)現(xiàn)Comparable接口可以利用其有序性特點(diǎn),提高集合使用/搜索/排序的性能
Contactsgn(x.compareTo(y)) == - sgn(y.compareTo(x)),當(dāng)類型不對時(shí),應(yīng)該拋出ClassCastException,拋出異常的行為應(yīng)該是一致的
transitive: x.compareTo(y) > 0 && y.compareTo(z) > 0 ==> x.compareTo(z) > 0
x.compareTo(y) == 0 ==> sgn(x.compareTo(z)) == sgn(y.compareTo(z))
建議,但非必須:與equals保持一致,即 x.compareTo(y) == 0 ==> x.equals(y),如果不一致,需要在文檔中明確指出

TreeSet, TreeMap等使用的就是有序保存,而HashSet, HashMap則是通過equals + hashCode保存
當(dāng)要為一個(gè)實(shí)現(xiàn)了Comparable接口的類增加成員變量時(shí),不要通過繼承來實(shí)現(xiàn),而是使用組合,并提供原有對象的訪問方法,以保持對Contract的遵循
實(shí)現(xiàn)細(xì)節(jié)優(yōu)先比較重要的域
謹(jǐn)慎使用返回差值的方式,有可能會溢出

Classes and Interfaces
Item 13: 最小化類、成員的可見性封裝(隱藏):公開的接口需要暴露,而接口的實(shí)現(xiàn)則需要隱藏,使得接口與實(shí)現(xiàn)解耦,降低模塊耦合度,增加可測試性、穩(wěn)定性、可維護(hù)性、可優(yōu)化性、可修改性

1.如果一個(gè)類只對一個(gè)類可見,則應(yīng)該將其定義為私有的內(nèi)部類,而沒必要public的類都應(yīng)該定義為package private
2.為了便于測試,可以適當(dāng)放松可見性,但也只應(yīng)該改為package private,不能更高
成員不能是非private的,尤其是可變的對象。一旦外部可訪問,將失去對其內(nèi)容的控制能力,而且會有多線程問題
3.暴露的常量也不能是可變的對象,否則public static final也將失去其意義,final成員無法改變其指向,但其指向的對象卻是可變的(immutable的對象除外),長度非0的數(shù)組同樣也是有問題的,可以考慮每次訪問時(shí)創(chuàng)建拷貝,或者使用Collections.unmodifiableList(Arrays.asList(arr))

Item 14: public class中,使用accessor method而非public field后者外部可以直接訪問,失去了安全性
package private或者private則可以不必這樣
把immutable的field置為public勉強(qiáng)可以接受,mutable的成員一定不能置為public

Item 15: 最小化可變性不提供可以改變本對象狀態(tài)的方法
保證類不可被繼承
使用final field
使用private field
在構(gòu)造函數(shù)、accessor中,對mutable field使用defensive copy
實(shí)現(xiàn)建議操作函數(shù),例如BigInteger的add方法,不是static的,但也不能改變本對象的狀態(tài),則使用functional的方式,返回一個(gè)新的對象,其狀態(tài)是本對象修改之后的狀態(tài)
如此實(shí)現(xiàn)的immutable對象生來就是線程安全的,無需同步操作,但應(yīng)該鼓勵(lì)共用實(shí)例,避免創(chuàng)建過多重復(fù)的對象
正確實(shí)現(xiàn)的immutable對象也不需要clone, copy方法;可以適當(dāng)引入Object cache;

劣勢每一個(gè)值都需要一個(gè)對象,調(diào)用改變狀態(tài)的方法而創(chuàng)建一個(gè)新的對象,尤其是它是重量級的,開銷會變大;連續(xù)調(diào)用這樣的方法,影響更大;
為常用的多次操作組合提供一個(gè)方法

其他保證class無法被繼承,除了聲明為final外,還可以將默認(rèn)構(gòu)造函數(shù)聲明為private或package private,然后提供public static工廠方法
使用public static工廠方法,具體實(shí)現(xiàn)類可以有多個(gè),還能進(jìn)行object cache
當(dāng)實(shí)現(xiàn)Serializable接口是,一定要實(shí)現(xiàn)readObject/readResolve方法,或者使用ObjectOutputStream.writeUnshared/ObjectInputStream.readUnshared

小結(jié)除非有很好的理由讓一個(gè)Class mutable,否則應(yīng)該使其immutable
如果非要mutable,也應(yīng)盡可能限制其可變性

Item 16: Favor composition (and forwarding) over inheritance跨包繼承、繼承不是被設(shè)計(jì)為應(yīng)該被繼承的實(shí)現(xiàn)類,是一件很危險(xiǎn)的事情,繼承接口、繼承抽象類,當(dāng)然是沒問題的
如果子類的功能依賴于父類的實(shí)現(xiàn)細(xì)節(jié),那么一旦父類發(fā)生變化,子類將有可能出現(xiàn)Bug,即便代碼都沒有修改;而設(shè)計(jì)為應(yīng)被繼承的類,在修改后,是應(yīng)該有文檔說明的,子類開發(fā)者既可以得知,也可以知道如何修改
例子:統(tǒng)計(jì)HashSet添加元素的次數(shù)用繼承方式,重寫add,addAll,在其中計(jì)數(shù),這就不對,因?yàn)镠ashSet內(nèi)部的addAll是通過調(diào)用add實(shí)現(xiàn)的
但是通過不重寫addAll也只不對的,以后有可能HashSet的實(shí)現(xiàn)就變了
在重寫中重新實(shí)現(xiàn)一遍父類的邏輯也是行不通的,因?yàn)檫@可能會導(dǎo)致性能問題、bug等,而且有些功能不訪問私有成員也是無法實(shí)現(xiàn)的
還有一個(gè)原因就是父類的實(shí)現(xiàn)中,可能會增加方法,改變其行為,而這一點(diǎn),在子類中是無法控制的

而通過組合的方式,將不會有這些問題,把另一個(gè)類的對象聲明為私有成員,外部將無法訪問它,自己也能在轉(zhuǎn)發(fā)(forwarding)過程中執(zhí)行攔截操作,也不必依賴其實(shí)現(xiàn)細(xì)節(jié),這種組合、轉(zhuǎn)發(fā)的實(shí)現(xiàn)被稱為wrapper,或者Decorator pattern,或者delegation(嚴(yán)格來說不是代理,代理一般wrapper對象都需要把自己傳入到被wrap的對象方法中?)
缺點(diǎn)不適用于callback frameworks?

繼承應(yīng)該在is-a的場景中使用
繼承除了會繼承父類的API功能,也會繼承父類的設(shè)計(jì)缺陷,而組合則可以隱藏成員類的設(shè)計(jì)缺陷

Item 17: Design and document for inheritance or else prohibit it一個(gè)類必須在文檔中說明,每個(gè)可重寫的方法,在該類的實(shí)現(xiàn)中的哪些地方會被調(diào)用(the class must document its self-use of overridable methods)。調(diào)用時(shí)機(jī)、順序、結(jié)果產(chǎn)生的影響,包括多線程、初始化等情況。
被繼承類應(yīng)該通過謹(jǐn)慎選擇protected的方法或成員,來提供一些hook,用于改變其內(nèi)部的行為,例如java.util.AbstractList::removeRange。
The only way to test a class designed for inheritance is to write subclasses. 用于判斷是否需要增加或者減少protected成員/方法,通常寫3個(gè)子類就差不多了。
You must test your class by writing subclasses before you release it.
Constructors must not invoke overridable methods. 父類的構(gòu)造函數(shù)比子類的構(gòu)造函數(shù)先執(zhí)行,而如果父類構(gòu)造函數(shù)中調(diào)用了可重寫的方法,那么就會導(dǎo)致子類的重寫方法比子類的構(gòu)造函數(shù)先執(zhí)行,會導(dǎo)致corruption。
如果實(shí)現(xiàn)了Serializable/Cloneable接口,neither clone nor readObject may invoke an overridable method, directly or indirectly. 重寫方法會在deserialized/fix the clone’s state之前執(zhí)行。
如果實(shí)現(xiàn)了Serializable接口,readResolve/writeReplace必須是protected,而非private
designing a class for inheritance places substantial limitations on the class.
The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. 聲明為final class或者把構(gòu)造函數(shù)私有化(提供public static工廠方法)。
如果確實(shí)想要允許繼承,就應(yīng)該為每個(gè)被自己使用的可重寫方法都寫好文檔

Item 18: Prefer interfaces to abstract classesJava類只允許單繼承,接口可以多繼承,使用接口定義類型,使得class hierarchy更加靈活
定義mixin(optional functionality to be "mixed in")時(shí)使用interface是很方便的,需要增加此功能的類只需要implement該接口即可,而如果使用抽象類,則無法增加一個(gè)extends語句
接口允許構(gòu)建沒有hierarchy的類型系統(tǒng)
使用接口定義類型,可以使得item 16中提到的wrapper模式更加安全、強(qiáng)大,
skeletal implementation:該類為abstract,把必須由client實(shí)現(xiàn)的方法設(shè)為abstract,可以有默認(rèn)實(shí)現(xiàn)的則提供默認(rèn)實(shí)現(xiàn)
simulated multiple inheritance:通過實(shí)現(xiàn)定義的接口,同時(shí)在內(nèi)部實(shí)現(xiàn)一個(gè)匿名的skeletal implementation,將對對該接口的調(diào)用轉(zhuǎn)發(fā)到匿名類中,起到“多繼承”的效果
simple implementation:提供一個(gè)非抽象的接口實(shí)現(xiàn)類,提供一個(gè)最簡單、能work的實(shí)現(xiàn),也允許被繼承
使用接口定義類型的缺點(diǎn):不便于演進(jìn),一旦接口發(fā)布,如果想要增加功能(增加方法),則client將無法編譯;而使用abstract class,則沒有此問題,只需要提供默認(rèn)實(shí)現(xiàn)即可
小結(jié)通過接口定義類型,可以允許多實(shí)現(xiàn)(多繼承)
但是演進(jìn)需求大于靈活性、功能性時(shí),抽象類更合適
提供接口時(shí),提供一個(gè)skeletal implementation,同時(shí)審慎考慮接口設(shè)計(jì)

Item 19: 僅僅用interface去定義一個(gè)類型,該接口應(yīng)該有實(shí)現(xiàn)類,使用者通過接口引用,去調(diào)用接口的方法避免用接口去定義常量,應(yīng)該用noninstantiable utility class去定義常量
相關(guān)常量的命名,通過公共前綴來實(shí)現(xiàn)分組

Item 20: Prefer class hierarchies to tagged classestagged class: 在內(nèi)部定義一個(gè)tag變量,由其控制功能的轉(zhuǎn)換
tag classes are verbose, error-prone, and inefficient
而class hierarchy,不同功能由不同子類實(shí)現(xiàn),公共部分抽象為一個(gè)基類,也能反映出各個(gè)子類之間的關(guān)系

Item 21: Use function objects to represent strategies只提供一個(gè)功能函數(shù)的類實(shí)例,沒有成員變量,只需一個(gè)對象(單例),為其功能定義一個(gè)接口,則可以實(shí)現(xiàn)策略模式,把具體策略傳入相應(yīng)函數(shù)中,使用策略
具體的策略實(shí)例通常使用匿名類定義,調(diào)用使用該策略的方法時(shí)才予以創(chuàng)建/預(yù)先創(chuàng)建好之后每次將其傳入

Item 22: Favor static member classes over nonstatic有4種nested class:non-static member class; static member class(inner class); anonymous class; local class
static member class經(jīng)常作為helper class,和外部類一起使用
如果nested class的生命周期獨(dú)立于外部類存在,則必須定義為static member class,否則可能造成內(nèi)存泄漏
private static member class用處一:表示(封裝)外部類的一些成員,例如Map的Entry內(nèi)部類。

non-static member class將持有外部類實(shí)例的強(qiáng)引用,可以直接引用外部類的成員和方法
用處一:定義一個(gè)Adapter,使得外部內(nèi)的實(shí)例,可以作為和外部類語義不同的實(shí)例來查看(訪問),例如Collection的Iterator。
如果nested class不需要引用外部類的成員和方法,則一定要將其定義為static,避免空間/時(shí)間開銷,避免內(nèi)存泄漏

anonymous class當(dāng)在非static代碼塊內(nèi)定義時(shí),會持有外部類的引用,否則不會持有
限制只能在被聲明的地方進(jìn)行實(shí)例化
無法進(jìn)行instanceof測試
不能用匿名類實(shí)現(xiàn)多個(gè)接口
不能用匿名類繼承一個(gè)類的同時(shí)實(shí)現(xiàn)接口
匿名類中新添加的方法無法在匿名類外部訪問
不能有static成員

應(yīng)該盡量保持簡短
用處一:創(chuàng)建function object
用處二:創(chuàng)建process object,例如:Runnable, Thread, TimberTask
用處三:用于public static工廠方法,例如Collections類里面的一些工廠方法,很多是返回一個(gè)匿名的內(nèi)部實(shí)現(xiàn)

local class比較少用
是否static取決于其定義的上下文
可以在作用域內(nèi)重復(fù)使用
不能有static成員
也應(yīng)盡量保持簡短

小結(jié)四種nested class
如果nested class在整個(gè)外部類內(nèi)都需要可見,或者定義代碼太長,應(yīng)使用member class
能static就一定要static,即便需要對外部類進(jìn)行引用,對于生命周期獨(dú)立于外部類的,也應(yīng)該通過WeakReference進(jìn)行引用,避免內(nèi)存泄漏;至于生命周期和外部類一致的,則不必這樣

Generics
Item 23: Don’t use raw types in new code
Java泛型,例如List<E>
,真正使用的時(shí)候都是List<String>
等,把E替換為實(shí)際的類型
Java泛型從1.5引入,為了保持兼容性,實(shí)現(xiàn)的是偽泛型,類型參數(shù)信息在編譯完成之后都會被擦除,其在運(yùn)行時(shí)的類型都是raw type,類型參數(shù)保存的都是Object類型,List<E>
的raw type就是List

編譯器在編譯期通過類型參數(shù),為讀操作自動進(jìn)行了類型強(qiáng)制轉(zhuǎn)換,同時(shí)在寫操作時(shí)自動進(jìn)行了類型檢查
如果使用raw type,那編譯器就不會在寫操作時(shí)進(jìn)行類型檢查了,寫入錯(cuò)誤的類型也不會報(bào)編譯錯(cuò)誤,那么在后續(xù)讀操作進(jìn)行強(qiáng)制類型轉(zhuǎn)換時(shí),將會導(dǎo)致轉(zhuǎn)換失敗,拋出異常
一旦錯(cuò)誤發(fā)生,應(yīng)該讓它盡早被知道(拋出/捕獲),編譯期顯然優(yōu)于運(yùn)行期
List
與List<Object>
的區(qū)別
前者不具備類型安全性,后者具備,例如以下代碼
// Uses raw type (List) - fails at runtime! public static void main(String[] args) { List<String> strings = new ArrayList<String>(); unsafeAdd(strings, new Integer(42)); String s = strings.get(0); // Compiler-generated cast } private static void unsafeAdd(List list, Object o) { list.add(o); }

不會報(bào)編譯錯(cuò)誤,但會給一個(gè)編譯警告:Test.java:10: warning: unchecked call to add(E) in raw type List list.add(o);
,而運(yùn)行時(shí)則會發(fā)生錯(cuò)誤。

但如果使用List<Object>
,即unsageAdd
參數(shù)改為List<Object> list, Object o
,則會報(bào)編譯錯(cuò)誤:Test.java:5: unsafeAdd(List<Object>,Object) cannot be applied to (List<String>,Integer) unsafeAdd(strings, new Integer(42));

因?yàn)長ist<String>
是List
的子類,但卻不是List<Object>
的子類。
并不是說這個(gè)場景應(yīng)該使用List<Object>
,這個(gè)場景應(yīng)該使用List<String>
,這里只是為了說明List
和List<Object>
是有區(qū)別的。

List
v.s. List<?>
(unbounded wildcard types),當(dāng)不確定類型參數(shù),或者說類型參數(shù)不重要時(shí),也不應(yīng)該使用raw type,而應(yīng)該使用List<?>

任何參數(shù)化的List均是List<?>
的子類,可以作為參數(shù)傳入接受List<?>
的函數(shù),例如以下代碼均是合法的:
void func(List<?> list) { ... } func(new List<Object>()); func(new List<Integer>()); func(new List<String>());

持有List<?>
的引用后,并不能向其中加入任何元素,讀取出來的元素也是Object
類型,而不會被自動強(qiáng)轉(zhuǎn)為任何類型。
如果List<?>
的行為不能滿足需求,可以考慮使用模板方法,或者List<E extends XXX>
(bounded wildcard types)

You must use raw types in class literals.List.class
, String[].class
, and int.class
are all legal, but List<String>.class
andList<?>.class
are not.

instanceof
不支持泛型,以下用法是推薦的,但不應(yīng)該將o
強(qiáng)轉(zhuǎn)為List
// Legitimate use of raw type - instanceof operator if (o instanceof Set) { // Raw type Set<?> m = (Set<?>) o; // Wildcard type ... }

相關(guān)術(shù)語匯總
java_generic_terms.png
java_generic_terms.png

Item 24: Eliminate unchecked warnings當(dāng)出現(xiàn)類型不安全的強(qiáng)制轉(zhuǎn)換時(shí)(一般都是涉及泛型,raw type),編譯器會給出警告,首先要做的是盡量消除不安全的轉(zhuǎn)換,消除警告
實(shí)在無法消除/確定不會導(dǎo)致運(yùn)行時(shí)的ClassCastException
,可以通過@SuppressWarnings("unchecked")
消除警告,但不要直接忽略該警告
使用@SuppressWarnings("unchecked")
時(shí),應(yīng)該在注視內(nèi)證明確實(shí)不存在運(yùn)行時(shí)的ClassCastException
;同時(shí)應(yīng)該盡量減小其作用的范圍,通常是應(yīng)該為一個(gè)賦值語句添加注解

Item 25: Prefer lists to arrays
arrays are covariant(協(xié)變): 如果Sub
是Super
的子類,那么Sub[]
也是Super[]
的子類
generics are invariant(不變): 任意兩個(gè)不同的類Type1
和Type2
,List<Type1>
和List<Type2>
之間沒有任何繼承關(guān)系
考慮以下代碼
// Fails at runtime!Object[] objectArray = new Long[1];objectArray[0] = "I don't fit in"; // Throws ArrayStoreException// Won't compile!List<Object> ol = new ArrayList<Long>(); // Incompatible typesol.add("I don't fit in");

arrays are reified(具體化): array在運(yùn)行時(shí)能知道且強(qiáng)制要求元素的類型
generics are implemented by erasure(non-reifiable): 僅僅在編譯時(shí)知道元素的類型
數(shù)組和泛型同時(shí)使用時(shí)會受到很大限制以下語句均不能通過編譯:new List<E>[], new List<String>[], new E[]
;但是聲明是可以的,例如List<String>[] stringLists

non-reifiable type: 例如E, List<E>, List<String>
,這些類型在運(yùn)行時(shí)的信息比編譯時(shí)的信息更少
只有unbounded wildcard type才是reifiable的,如:List<?>, Map<?, ?>

常規(guī)來說,不能返回泛型元素的數(shù)組,因?yàn)闀?bào)編譯錯(cuò)誤:generic array creation errors

當(dāng)泛型和varargs
一起使用時(shí),也會導(dǎo)致編譯警告
有時(shí)為了類型安全,不得不做些妥協(xié),犧牲性能和簡潔,使用List而不是數(shù)組
把數(shù)組強(qiáng)轉(zhuǎn)為non-reifiable類型是非常危險(xiǎn)的,僅應(yīng)在非常確定類型安全的情況下使用

Item 26: Favor generic types當(dāng)需要一個(gè)類成員的數(shù)據(jù)類型具備一般性時(shí),應(yīng)該用泛型,這也正是泛型的設(shè)計(jì)場景之一,不應(yīng)該用Object類
但使用泛型有時(shí)也不得不進(jìn)行cast,例如當(dāng)泛型遇上數(shù)組
總的來說把suppress數(shù)組類型強(qiáng)轉(zhuǎn)的unchecked warning比suppress一個(gè)標(biāo)量類型強(qiáng)轉(zhuǎn)的unchecked warning風(fēng)險(xiǎn)更大,但有時(shí)出于代碼簡潔性考慮,也不得不做出妥協(xié)
有時(shí)看似與item 25矛盾,實(shí)屬無奈,Java原生沒有List,ArrayList不得不基于數(shù)組實(shí)現(xiàn),HashMap也是基于數(shù)組實(shí)現(xiàn)的
泛型比使用者進(jìn)行cast更加安全,而且由于Java泛型的擦除實(shí)現(xiàn),也可以和未做泛型的老代碼無縫兼容

Item 27: Favor generic methods泛型方法的類型參數(shù)在函數(shù)修飾符(可見性/static/final等)和返回值之間,例子:// Generic methodpublic static <E> Set<E> union(Set<E> s1, Set<E> s2) { Set<E> result = new HashSet<>(s1); result.addAll(s2); return result;}

recursive type bound// Using a recursive type bound to express mutual comparabilitypublic static <T extends Comparable<T>> T max(List<T> list) {...}

泛型方法要比方法使用者進(jìn)行cast更加安全

Item 28: Use bounded wildcards to increase API flexibility
考慮以下代碼
public class Stack<E> { public Stack(); public void push(E e); public E pop(); public boolean isEmpty(); public void pushAll(Iterable<E> src); public void popAll(Collection<E> dst);}Stack<Number> numberStack = new Stack<Number>();Iterable<Integer> integers = ... ;numberStack.pushAll(integers);Stack<Number> numberStack = new Stack<Number>();Collection<Object> objects = ... ;numberStack.popAll(objects);

pushAll和popAll的調(diào)用均無法通過編譯,因?yàn)楸M管Integer
是Number
的子類,但I(xiàn)terable<Integer>
不是Iterable<Number>
的子類,這是由泛型的invariant特性導(dǎo)致的,所以Iterable<Integer>
不能傳入接受Iterable<Number>
參數(shù)的函數(shù),popAll的使用同理

bounded wildcards: <? extends E>
, <? super E>
, PECS stands for producer-extends, consumer-super. 如果傳入的參數(shù)是要輸入給該類型數(shù)據(jù)的,則應(yīng)該使用extends,如果是要容納該類型數(shù)據(jù)的輸出,則應(yīng)該使用super
這很好理解,作為輸入是要賦值給E類型的,當(dāng)然應(yīng)該是E的子類(這里的extends包括E類型本身);而容納輸出是要把E賦值給傳入?yún)?shù)的,當(dāng)然應(yīng)該是E的父類(同樣包括E本身)
返回值類型不要使用bounded wildcards,否則使用者也需要使用,這將會給使用者造成麻煩
代碼對于bounded wildcards的使用在使用者那邊應(yīng)該是透明的,即他們不會感知到bounded wildcards的存在,如果他們也需要考慮bounded wildcards的問題,則說明對bounded wildcards的使用有問題了
有時(shí)候編譯器的類型推導(dǎo)在遇到bounded wildcards會無法完成,這時(shí)就需要顯示指定類型信息,例如:
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2);Set<Integer> integers = ... ;Set<Double> doubles = ... ;//Set<Number> numbers = union(integers, doubles); //compile errorSet<Number> numbers = Union.<Number>union(integers, doubles); //compile pass

Comparables are always consumers, so you should always use Comparable<? super T>
in preference to Comparable<T>
. The same is true of comparators, so you should always useComparator<? super T>
in preference to Comparator<T>
.
unbounded type parameter(<E> ... List<E>
) v.s. unbounded wildcard(List<?>
):if a type parameter appears only once in a method declaration, replace it with a wildcard.

Item 29: Consider typesafe heterogeneous containers
使用泛型時(shí),類型參數(shù)是有限個(gè)的,例如List<T>
,Map<K, V>
,但有時(shí)可能需要一個(gè)容器,能放入任意類型的對象,但需要具備類型安全性,例如數(shù)據(jù)庫的一行,它的每一列都可能是任意類型的數(shù)據(jù)
由于Class
類從1.5就被泛型化了,所以使得這種需求可以實(shí)現(xiàn),例如:// Typesafe heterogeneous container pattern - APIpublic class Favorites { public <T> void putFavorite(Class<T> type, T instance); public <T> T getFavorite(Class<T> type);}

通常這樣使用的Class
對象被稱為type token,它傳入函數(shù),用來表述編譯時(shí)和運(yùn)行時(shí)的類型信息
Favorites
的實(shí)現(xiàn)也是很簡單的:
// Typesafe heterogeneous container pattern - implementationpublic class Favorites { private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>(); public <T> void putFavorite(Class<T> type, T instance) { if (type == null) throw new NullPointerException("Type is null"); favorites.put(type, instance); } public <T> T getFavorite(Class<T> type) { return type.cast(favorites.get(type)); }}

注意,這里的unbound wildcard并不是應(yīng)用于Map的,而是應(yīng)用于Class的類型參數(shù),因此Map可以put key進(jìn)去,而且key可以是任意類型參數(shù)的Class對象
另外,Map的value類型是Object,一旦put到Map中去,其編譯期類型信息就丟失了,將通過get方法的動態(tài)類型轉(zhuǎn)換(cast)來重新獲得其類型信息
cast方法將檢查類型信息,如果是該類型(或其子類),轉(zhuǎn)換將成功,并返回引用,否則將拋出ClassCastException
這一heterogeneous container實(shí)現(xiàn)有兩個(gè)不足通過為put方法傳入Class的raw type,使用者可以很輕易地破壞類型安全性,解決方案也很簡單,在put時(shí)也進(jìn)行一下cast:// Achieving runtime type safety with a dynamic castpublic <T> void putFavorite(Class<T> type, T instance) { favorites.put(type, type.cast(instance));}

這樣做的效果是使得想要破壞類型安全性的put使用者產(chǎn)生異常,而使用get的使用者則不會因?yàn)閻阂鈖ut使用者產(chǎn)生異常。這種做法也被java.util.Collections
包中的一些方法使用,例如命名為checkedSet, checkedList, checkedMap的類。

這個(gè)容器內(nèi)不能放入non-reifiable的類型,例如List<String>
,因?yàn)長ist<String>.class
是有語法錯(cuò)誤的,List<String>
, List<Integer>
都只有同一個(gè)class對象:List.class
;另外String[].class
是合法的。

Favorites
使用的類型參數(shù)是unbounded的,可以put任意類型,也可以使用bounded type token,使用bounded時(shí)可能需要把Class<?>
轉(zhuǎn)換為Class<? extends Annotation>
,直接用class.cast
將會導(dǎo)致unchecked warning,可以通過class.asSubclass
來進(jìn)行轉(zhuǎn)換,例子:// Use of asSubclass to safely cast to a bounded type tokenstatic Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) { Class<?> annotationType = null; // Unbounded type token try { annotationType = Class.forName(annotationTypeName); } catch (Exception ex) { throw new IllegalArgumentException(ex); } return element.getAnnotation(annotationType.asSubclass(Annotation.class));}

Enums and Annotations
Item 30: Use enums instead of int constants
類型安全
可以為常量提供數(shù)據(jù)和方法的綁定
可以遍歷
實(shí)現(xiàn)建議
如果是通用的,應(yīng)該定義為top level enum,否則應(yīng)定義為內(nèi)部類
constant-specific method implementations// Enum type with constant-specific method implementationspublic enum Operation { PLUS { double apply(double x, double y){return x + y;} }, MINUS { double apply(double x, double y){return x - y;} }, TIMES { double apply(double x, double y){return x * y;} }, DIVIDE { double apply(double x, double y){return x / y;} }; abstract double apply(double x, double y);}

結(jié)合constant-specific data
// Enum type with constant-specific class bodies and datapublic enum Operation { PLUS("+") { double apply(double x, double y) { return x + y; } }, MINUS("-") { double apply(double x, double y) { return x - y; } }, TIMES("*") { double apply(double x, double y) { return x * y; } }, DIVIDE("/") { double apply(double x, double y) { return x / y; } }; private final String symbol; Operation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } abstract double apply(double x, double y);}

If switch statements on enums are not a good choice for implementing con- stant-specific behavior on enums, what are they good for? Switches on enums are good for augmenting external enum types with constant-specific behavior.

A minor performance disadvantage of enums over int constants is that there is a space and time cost to load and initialize enum types.
所以,在安卓設(shè)備(手機(jī)、平板)上,應(yīng)該避免使用enum,減小空間和時(shí)間的開銷

Item 31: Use instance fields instead of ordinals每個(gè)enum的常量都有一個(gè)ordinal()
方法獲取其在該enum類型中的位置,但該方法只應(yīng)該在實(shí)現(xiàn)EnumSet
, EnumMap
等類型的時(shí)候被使用,其他情形都不應(yīng)該被使用
如果需要為每一個(gè)常量綁定一個(gè)數(shù)據(jù),可以使用instance field實(shí)現(xiàn),如果需要綁定方法,則可以用constant-specific method implementations,參考上一個(gè)item

Item 32: Use EnumSet instead of bit fieldsbit fields的方式不優(yōu)雅、容易出錯(cuò)、沒有類型安全性
EnumSet則沒有這些缺點(diǎn),而且對于大多數(shù)enum類型來說,其性能都和bit field相當(dāng)
通用建議:聲明變量時(shí),不要用實(shí)現(xiàn)類型,應(yīng)該用接口類型,例如,應(yīng)該用List<Integer>
而不是ArrayList<Integer>

EnumSet并非immutable的,可以通過Conllections.unmodifiableSet
來封裝為immutable,但是代碼簡潔性與性能都將受到影響

Item 33: Use EnumMap instead of ordinal indexing
同前文所述,應(yīng)該避免使用ordinal。當(dāng)需要用enum作為下標(biāo)從數(shù)組獲取數(shù)據(jù)時(shí),可以換個(gè)角度思考,以enum作為key從map里面獲取數(shù)據(jù)。
數(shù)組和泛型不兼容,因此使用數(shù)組也會導(dǎo)致編譯警告;而且ordinal的值本來就不是表達(dá)index含義的,極易導(dǎo)致隱蔽錯(cuò)誤
EnumMap內(nèi)部使用數(shù)組實(shí)現(xiàn),因此性能和數(shù)組相當(dāng)
使用數(shù)組也會導(dǎo)致程序可擴(kuò)展性下降,考慮以下兩種實(shí)現(xiàn)
// Using ordinal() to index array of arrays - DON'T DO THIS!public enum Phase { SOLID, LIQUID, GAS; public enum Transition { MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT; // Rows indexed by src-ordinal, cols by dst-ordinal private static final Transition[][] TRANSITIONS = { { null, MELT, SUBLIME }, { FREEZE, null, BOIL }, { DEPOSIT, CONDENSE, null } }; // Returns the phase transition from one phase to another public static Transition from(Phase src, Phase dst) { return TRANSITIONS[src.ordinal()][dst.ordinal()]; } }}// Using a nested EnumMap to associate data with enum pairspublic enum Phase { SOLID, LIQUID, GAS; public enum Transition { MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID), SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID); final Phase src; final Phase dst; Transition(Phase src, Phase dst) { this.src = src; this.dst = dst; } // Initialize the phase transition map private static final Map<Phase, Map<Phase,Transition>> m = new EnumMap<Phase, Map<Phase,Transition>>(Phase.class); static { for (Phase p : Phase.values()) m.put(p,new EnumMap<Phase,Transition>(Phase.class)); for (Transition trans : Transition.values()) m.get(trans.src).put(trans.dst, trans); } public static Transition from(Phase src, Phase dst) { return m.get(src).get(dst); } }}

當(dāng)需要增加Phase
時(shí),前者需要謹(jǐn)慎地修改TRANSITIONS
數(shù)組的內(nèi)容(這一步驟容易出錯(cuò)),而后者則只需要增加相應(yīng)Transition
即可,from
函數(shù)的邏輯完全不受影響。

Item 34: Emulate extensible enums with interfaces當(dāng)enum遇到可擴(kuò)展性時(shí),總是一個(gè)糟糕的問題;擴(kuò)展類是基礎(chǔ)類的實(shí)例,但反過來不是,這一點(diǎn)很讓人困惑;想要枚舉所有基礎(chǔ)類和擴(kuò)展類的enum對象時(shí),并沒有一個(gè)很好地辦法;
而對于可擴(kuò)展性的需求,是真實(shí)存在的,例如:operation codes (opcodes)
實(shí)現(xiàn)方式是通過定義一個(gè)接口,enum類型(基礎(chǔ)與擴(kuò)展)均實(shí)現(xiàn)該接口,而在使用enum的地方,接收這個(gè)接口作為參數(shù)
enum類型是不可擴(kuò)展的,但是interface具備可擴(kuò)展性,如果API使用接口而非實(shí)現(xiàn)去代表operation,API就有了可擴(kuò)展性
泛型高級用法:<T extends Enum<T> & Operation> ... Class<T>
,T類型是enum類型,且是Operation
子類
這一方式的不足:enum類型對接口的實(shí)現(xiàn)是不能繼承的

Item 35: Prefer annotations to naming patterns在1.5之前,naming patterns很常見,在JUnit中都是這樣,例如要求測例方法一test
開頭
naming patterns有很多問題拼寫錯(cuò)誤不能及時(shí)發(fā)現(xiàn)
無法保證naming patterns只在正確的場景使用,例如可能有人以test
開頭命名測例類,方法卻沒有,JUnit則不會運(yùn)行測例
沒有值/類型信息,編譯器無法提前發(fā)現(xiàn)問題

使用annotations可以很好的解決這些問題,但是annotations的功能也是有限的@Retention(RetentionPolicy.RUNTIME)
能限定其保留時(shí)期
@Target(ElementType.METHOD)
能限定其應(yīng)用的程序元素
還有其他meta-annotations,如@IntDef

annotations接收的參數(shù)如果是數(shù)組,為其賦值一個(gè)單獨(dú)的元素也是合法的

Item 36: Consistently use the Override annotation@Override
會使得重寫的準(zhǔn)確性得到檢查
重載和重寫的區(qū)別:一個(gè)只是函數(shù)名一樣,通過參數(shù)列表決定執(zhí)行哪個(gè)版本,是編譯時(shí)多態(tài);一個(gè)是通過虛函數(shù)機(jī)制實(shí)現(xiàn),是運(yùn)行時(shí)多態(tài);

Item 37: Use marker interfaces to define types定義一個(gè)空的接口,表明某個(gè)類型的屬性,例如Serializable

另一種方式是使用annotation,表明者其具有某種屬性
marker interface的優(yōu)點(diǎn)定義了一個(gè)類型,可以進(jìn)行instanceof判斷,可以聲明參數(shù)類型
比annotation更簡潔

marker annotation的優(yōu)點(diǎn)當(dāng)一個(gè)類型(通過interface或者annotation)被聲明后,如果想要加入更多的信息,annotation更方便,即annotation對修改是開放的,因?yàn)樗膶傩钥梢杂心J(rèn)值,而interface則不行,定義了方法就必須實(shí)現(xiàn)
annotation可以被應(yīng)用到更多代碼的元素中,不僅僅是類型

實(shí)現(xiàn)建議如果僅僅只應(yīng)用于類型,則應(yīng)該優(yōu)先考慮annotation
如果希望mark的對象被限定于某個(gè)接口的實(shí)例(即為一個(gè)接口增加另外一種語義,卻不改變其API),可以考慮使用marker interface

函數(shù)
Item 38: Check parameters for validity一個(gè)函數(shù)(包括構(gòu)造函數(shù))首先要做的事情就是驗(yàn)證參數(shù)合法性,如果不合法則應(yīng)該拋出相應(yīng)異常,這是對“盡早發(fā)現(xiàn)錯(cuò)誤盡早拋出”原則的遵循,否則等到錯(cuò)誤發(fā)生時(shí)將可能難以判斷錯(cuò)誤的根源所在,甚至程序不會顯式報(bào)錯(cuò),而是執(zhí)行了錯(cuò)誤的行為,導(dǎo)致更嚴(yán)重的后果
不由被調(diào)用函數(shù)使用,而是存起來留作后用的參數(shù),更加要檢查其合法性
Javadoc里面應(yīng)該注明@throw
項(xiàng),并說明原因
非公開的API(private或package private),則不應(yīng)該通過拋異常來報(bào)錯(cuò),應(yīng)該采用assert
,assert可以通過配置虛擬機(jī)參數(shù)開啟或關(guān)閉,如果關(guān)閉則不會被執(zhí)行
靈活運(yùn)用,設(shè)計(jì)API時(shí),就應(yīng)該盡量設(shè)計(jì)得通用一些,即可以接受更大范圍的參數(shù),畢竟檢查參數(shù)也是有開銷的
另外可以考慮拋出RuntimeException
的子類,因?yàn)檫@樣的異常不用放到函數(shù)的異常表中,函數(shù)的使用者也不用必須try-catch
或者throw
,但doc一定要寫明

Item 39: Make defensive copies when needed編碼一大原則:永遠(yuǎn)不要信任用戶(調(diào)用方)輸入的數(shù)據(jù),也不要信任它們不會篡改返回的數(shù)據(jù),因此defensive copy很有必要
編寫一個(gè)類時(shí),如果成員變量是mutable的,那么就需要在構(gòu)造函數(shù)(或者setter)中進(jìn)行深拷貝,并且,先拷貝,再驗(yàn)證已拷貝數(shù)據(jù)的合法性(既不是先驗(yàn)證,也不是驗(yàn)證傳入的數(shù)據(jù),避免TOCTOU attack)
另外深拷貝時(shí),傳入對象的類如果不是final的,就不能用clone方法進(jìn)行拷貝,因?yàn)椴荒鼙WCclone方法返回的就正好是這個(gè)類的實(shí)例(有可能會是惡意的子類)
為mutable成員提供getter方法時(shí),返回前也要進(jìn)行深拷貝,但此時(shí)可以用clone方法,因?yàn)槲覀兇_定成員就是我們想要的類的對象
java內(nèi)建的Map, Set等容器,實(shí)現(xiàn)上是沒有進(jìn)行深拷貝的,因?yàn)槭欠盒?,所以put進(jìn)去或者get出來的時(shí)候,編譯期都不知道具體是什么類型,是無法調(diào)用構(gòu)造函數(shù)的,如果想要測試這一問題,需要確定key和value的類型都是mutable的,如果測Map<String, Integer>
,那結(jié)果肯定是錯(cuò)誤的,但如果測Map<StringBuilder, Date>
,就可以知道確實(shí)如此;所以如果要把用戶傳入的數(shù)據(jù)放入Map,且key/value是mutable的,那么就需要在put之前進(jìn)行深拷貝,否則可能會被用戶attack
長度非零的數(shù)組都是mutable的
盡量使用immutable的成員就可以省去深拷貝帶來的性能開銷
如果確實(shí)信任用戶,就可以把深拷貝省去,但一定要在文檔內(nèi)說明,例如:wrapper模式,如果用戶惡意,那損害的也就僅僅是其自身;或者用戶都是自己的代碼,可以確信安全。

Item 40: Design method signatures carefully命名要合理,可理解:清除表達(dá)函數(shù)的功能;符合常識;保持風(fēng)格一致;
類/接口的成員方法數(shù)量不要太多,否則會令人難以理解,而且不利于測試、維護(hù)
不要隨便提供helper方法,只有當(dāng)很有必要時(shí)才提供
避免過長參數(shù)列表(不多于4個(gè)),尤其是參數(shù)類型相同,否則既難記(倒還好),又可能引起隱晦的bug(傳入?yún)?shù)順序錯(cuò)了,編譯不報(bào)錯(cuò),運(yùn)行時(shí)行為確是錯(cuò)的)可以通過把參數(shù)列表過長的方法拆分為幾個(gè)方法,但要避免導(dǎo)致方法過多
創(chuàng)建helper類,容納作用相關(guān)聯(lián)的的參數(shù)
類似于構(gòu)造對象的Builder模式,為函數(shù)的調(diào)用創(chuàng)建一個(gè)builder

參數(shù)類型,使用interface,而不是實(shí)現(xiàn)類
對于起控制作用的參數(shù),使用二值enum,而不是boolean,便于擴(kuò)展;對于安卓來說,可以通過@IntDef
輔助定義int常量,模擬enum

Item 41: Use overloading judiciously慎用重載,重載(overload)與重寫(override)的區(qū)別可以見上文,簡言之,前者編譯時(shí)多態(tài),后者運(yùn)行時(shí)多態(tài)
重載是編譯時(shí)多態(tài),版本選擇在編譯期完成,根據(jù)編譯期參數(shù)的類型信息來進(jìn)行決策
建議不要用參數(shù)類型來設(shè)計(jì)不同的重載版本,應(yīng)該通過參數(shù)列表長度,或者沒有父子類關(guān)系的不同參數(shù)類型,例如接受int和float的類型,后者也還是可能會有問題

Item 42: Use varargs judiciously
varargs的原理是調(diào)用時(shí)首先創(chuàng)建一個(gè)數(shù)組,然后把傳入的參數(shù)放入數(shù)組,數(shù)組長度為參數(shù)個(gè)數(shù)
一個(gè)方法需要0或多個(gè)同類型數(shù)據(jù)這個(gè)需求很常見,然而也有另一個(gè)很常見的需求:需要一個(gè)或多個(gè)同類型數(shù)據(jù),此時(shí)單純用varargs不太優(yōu)雅,可以讓方法先接受一個(gè)數(shù)據(jù),在接受一個(gè)varargs
varargs最初是為了printf和反射設(shè)計(jì)的
可以通過把傳入?yún)?shù)從一個(gè)數(shù)組改為varargs,來改良該方法(變得更靈活),而且對已有代碼“無影響”,Arrays.asList
便是一個(gè)例子,但接受varargs最初是為了打印數(shù)組內(nèi)容設(shè)計(jì)的,而不是為了把多個(gè)數(shù)據(jù)變成一個(gè)List
Don’t retrofit every method that has a final array parameter; use varargs only when a call really operates on a variable-length sequence of values.
以下兩種函數(shù)聲明都可能會產(chǎn)生問題:
ReturnType1 suspect1(Object... args) { }<T> ReturnType2 suspect2(T... args) { }

如果傳入一個(gè)基本類型的數(shù)組進(jìn)去(例如int[]),那么這兩個(gè)方法接受的都是一個(gè)int[][],即相當(dāng)于接受了一個(gè)只有一個(gè)元素的數(shù)組,而這個(gè)數(shù)組的數(shù)據(jù)類型是int[]!而如果傳入一個(gè)對象的數(shù)組,則相當(dāng)于傳入了數(shù)組長度個(gè)數(shù)的varargs。Arrays.asList
方法就存在這個(gè)問題!

varargs也存在性能影響,因?yàn)槊看握{(diào)用都會創(chuàng)建、初始化一個(gè)數(shù)組。如果為了不失API靈活性,同時(shí)大部分調(diào)用的參數(shù)個(gè)數(shù)都是有限個(gè),例如03個(gè),那么可以聲明5個(gè)重載版本,分別接受03個(gè)參數(shù),另外加一個(gè)3個(gè)參數(shù)+varargs的版本

Item 43: Return empty arrays or collections, not nulls
可能有人認(rèn)為返回null能減小內(nèi)存開銷,然:永遠(yuǎn)不要過度考慮性能問題,只有當(dāng)profiling顯示瓶頸就是這里的時(shí)候,再考慮性能優(yōu)化與代碼優(yōu)雅性的犧牲,當(dāng)然,無副作用的優(yōu)化肯定盡早采納
可以每次需要返回空數(shù)組/集合時(shí),返回同一個(gè)空數(shù)組/集合,這樣就只需要一次內(nèi)存分配

Collection的<T> T[] toArray(T[] a)
方法,可以每次調(diào)用時(shí)傳入一個(gè)空數(shù)組,因?yàn)樵摲椒ūWC如果集合元素可以被放入提供的參數(shù)數(shù)組中,將不會分配新內(nèi)存,當(dāng)放不下時(shí)才會分配
下面實(shí)現(xiàn)返回集合的值的方式也是值得借鑒的

// The right way to return a copy of a collection public List<Cheese> getCheeseList() { if (cheesesInStock.isEmpty()) return Collections.emptyList(); // Always returns same list else return new ArrayList<Cheese>(cheesesInStock); }

Item 44: Write doc comments for all exposed API elements對于API暴露的部分(類、接口、方法、成員等),都應(yīng)該先寫好文檔;為了提高代碼的可維護(hù)性,未暴露的部分也應(yīng)該寫好文檔;
每個(gè)方法的文檔的內(nèi)容,應(yīng)該是描述該方法與調(diào)用者之間的約定,不必是實(shí)現(xiàn)細(xì)節(jié),細(xì)節(jié)可以看代碼,約定則是使用者關(guān)心的東西;設(shè)計(jì)為被繼承的類,方法文檔應(yīng)該描述該方法做了什么,而不是怎么做的;
方法的文檔中,應(yīng)該描述約定的前提條件,執(zhí)行后產(chǎn)生的影響,尤其是對于“系統(tǒng)”(或者說這個(gè)對象)狀態(tài)的影響;不符合前提條件的情形將拋出異常;
更多細(xì)節(jié)@param
, @return
, @throws
描述不要句號結(jié)尾
@throws
的描述應(yīng)該以if開頭,其他都應(yīng)該是名詞描述
@{code}
與@{literal}

有泛型時(shí),需要說明每個(gè)類型參數(shù)
enum類型要為每個(gè)常量注釋含義
annotation的定義,要為每個(gè)成員/參數(shù)注釋含義
線程安全性說明,可見性說明,序列化說明

編程通用
Item 45: Minimize the scope of local variables在變量第一次使用的時(shí)候進(jìn)行聲明,聲明時(shí)盡量就進(jìn)行初始化
因此也更傾向于使用for-loop,而不是while-loop,因?yàn)楹笳咝枰褂脀hile-loop外定義的控制變量
for-loop的終結(jié)條件變量n,也應(yīng)該在循環(huán)變量i初始化時(shí)計(jì)算,避免重復(fù)計(jì)算
保持方法簡短,一個(gè)方法只做一件事

Item 46: Prefer for-each loops to traditional for loops
優(yōu)點(diǎn)之一:可以避免一些容易犯的bug
// Can you spot the bug? enum Suit { CLUB, DIAMOND, HEART, SPADE } enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING } ... Collection<Suit> suits = Arrays.asList(Suit.values()); Collection<Rank> ranks = Arrays.asList(Rank.values()); List<Card> deck = new ArrayList<Card>(); for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) deck.add(new Card(i.next(), j.next()));

i.next()
在內(nèi)層循環(huán)被調(diào)用了多次。以下寫法則直觀且不易出錯(cuò):
// Preferred idiom for nested iteration on collections and arrays for (Suit suit : suits) for (Rank rank : ranks) deck.add(new Card(suit, rank));

此種寫法不僅可用于集合和數(shù)組,任何實(shí)現(xiàn)Iterable
接口的類都可以用于冒號后面的部分
缺點(diǎn)有性能代價(jià)!一定會創(chuàng)建Iterator,對于安卓開發(fā),不建議如此。
不能在for-each語法中進(jìn)行remove,用Iterator遍歷時(shí),能remove
遍歷過程中替換原有元素
Parallel iteration

Item 47: Know and use the librariesdon't reinvent the wheel
視野!

Item 48: Avoid float and double if exact answers are requiredfloat和double設(shè)計(jì)為用于科學(xué)計(jì)算,“精確近似”,需要確切結(jié)果的,不要使用,例如:貨幣相關(guān)!應(yīng)該使用BigDecimal, int, 或者long。
BigDecimal使用有些不方便,性能也比primitive類型低

Item 49: Prefer primitive types to boxed primitives兩種類型的區(qū)別boxed類型,除了包含數(shù)值外,還有不同的唯一標(biāo)示,即值一樣,對象可以不一樣,這一點(diǎn)很重要!
boxed類型,比primitive類型多一個(gè)值,null
boxed類型,時(shí)間、空間效率均低一些

caveats有些操作會auto-unbox,例如:加減乘除,大小比較,但判等(==
)不會!
Applying the ==
operator to boxed primitives is almost always wrong.
boxed類型,值為null時(shí),會unbox為什么呢?會拋出NullPointerException

當(dāng)boxed和primitive出現(xiàn)在同一個(gè)運(yùn)算中,boxed類型會auto-unbox(包括判等)
大量重復(fù)的box/unbox會導(dǎo)致性能大幅下降

使用場景與注意事項(xiàng)放到標(biāo)準(zhǔn)集合里面,必須是boxed類型
作為類型參數(shù)(泛型),必須是boxed類型
auto-box是安全的,也能省去繁瑣的代碼,但是auto-unbox則可能引起隱蔽的錯(cuò)誤

Item 50: Avoid strings where other types are more appropriateStrings are poor substitutes for other value types. 只有當(dāng)數(shù)據(jù)確實(shí)就是文本時(shí),才適合用String。
Strings are poor substitutes for enum types.
Strings are poor substitutes for aggregate types. 把一系列數(shù)據(jù)轉(zhuǎn)化為一個(gè)String(序列化),然后再反序列化,也應(yīng)該用Json,如果自定義分隔符,既不優(yōu)雅,也不安全。
Strings are poor substitutes for capabilities. capability是一種稱呼,通常就是說不同的對象,憑借一個(gè)key去同一個(gè)地方保存、獲取數(shù)據(jù);如果用String,那么如果內(nèi)容相同,那key就會沖突,不安全;ThreadLocal的發(fā)展史*。

Item 51: Beware the performance of string concatenation用+
連接n個(gè)String,時(shí)間復(fù)雜度為O(n^2)
,因?yàn)镾tring是immutable的,所以每次拼接都會拷貝兩者的內(nèi)容
使用StringBuilder
進(jìn)行拼接操作;不過對于安卓開發(fā)來說,基本沒什么影響,因?yàn)樵诖虬倪^程中,這一優(yōu)化會自動完成;

Item 52: Refer to objects by their interfaces如果有接口,那么函數(shù)參數(shù)、返回值、成員變量、局部變量,都應(yīng)該使用接口來保持對象的引用,只有在通過構(gòu)造函數(shù)創(chuàng)建對象時(shí)才應(yīng)該引用具體的實(shí)現(xiàn)類型;面向接口編程更廣義的實(shí)踐;
面向接口編程使得程序更加靈活,切換實(shí)現(xiàn)類非常簡單;但如果代碼功能/正確性依賴于實(shí)現(xiàn)類特有的特性,那么切換時(shí)就需要仔細(xì)考慮一下;
當(dāng)然,如果對應(yīng)功能的接口不存在,那直接引用該類當(dāng)然是可以的;value type; class-based framework; 或者實(shí)現(xiàn)類提供了接口不存在的功能

Item 53: Prefer interfaces to reflection反射可以訪問私有成員
反射可以調(diào)用編譯時(shí)不存在的類的方法,當(dāng)然需要運(yùn)行時(shí)已經(jīng)加載
但是反射也是有代價(jià)的編譯期的類型檢查完全失效,類型安全性喪失
反射代碼繁瑣且易出錯(cuò),當(dāng)然這一點(diǎn)有一些好的框架可以避免,例如JOOR
性能下降,反射調(diào)用性能會低很多

反射常用的場景class browsers, object inspectors, code analysis tools, and interpretive embedded systems, remote procedure call (RPC) systems
反射功能強(qiáng)大,也有一些不足,如果合適利用,還是非常方便的
例如編譯期有些類尚未獲得,但是如果有其父類/接口,則可以聲明為父類/接口,只通過反射創(chuàng)建實(shí)例,其余代碼都無需反射

Item 54: Use native methods judiciously設(shè)計(jì)之初的三大用途訪問平臺相關(guān)的功能,例如registries and file locks
訪問老的C/C++版本的庫,訪問老的數(shù)據(jù)
追求性能

近年來JVM/Java的發(fā)展,性能已有很大改善,追求性能而使用JNI通常來說都已經(jīng)沒必要了
JNI的劣勢不安全,內(nèi)存管理不受JVM控制了,溢出等問題都有可能發(fā)生了
平臺相關(guān)
難以調(diào)試
Java和native層的交互是有開銷的
native代碼比Java代碼更難懂

對于安卓應(yīng)用開發(fā)來說,JNI還有一點(diǎn)就是隱藏實(shí)現(xiàn),Java代碼反編譯非常容易,而native代碼則難一些

Item 55: Optimize judiciously只有當(dāng)確實(shí)需要時(shí),才考慮性能優(yōu)化,當(dāng)然一些常見的范式,初次編碼時(shí)就應(yīng)該遵循
Strive to write good programs rather than fast ones; speed will follow.
Strive to avoid design decisions that limit performance.
Consider the performance consequences of your API design decisions.
It is a very bad idea to warp an API to achieve good performance.
當(dāng)確實(shí)需要優(yōu)化性能時(shí):measure performance before and after each attempted optimization.
找到原因后,首先考慮的是算法的優(yōu)化,然后是上層的優(yōu)化
在進(jìn)行優(yōu)化前,對程序進(jìn)行profiling,確定瓶頸,否則可能浪費(fèi)精力反而性能下降

Item 56: Adhere to generally accepted naming conventions包名要體現(xiàn)出組件的層次結(jié)構(gòu),全小寫
公布到外部的,包名以公司/組織的域名開頭,例如:edu.cmu, com.sun
...

異常處理
Item 57: Use exceptions only for exceptional conditionsexceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow.
A well-designed API must not force its clients to use exceptions for ordinary control flow.如果一個(gè)類的某個(gè)方法,依賴于該類當(dāng)前處于某個(gè)特定狀態(tài),則應(yīng)該提供一個(gè)單獨(dú)的狀態(tài)檢查方法,例如Iterator的next和hasNext方法
另外如果不提供狀態(tài)檢查方法,也可以讓方法在異常狀態(tài)下,返回一個(gè)特定的非法值
如果該類被并發(fā)訪問,且訪問時(shí)未進(jìn)行互斥處理,則必須使用返回非法值的方式;另外考慮到性能因素,也更傾向于返回非法值;其他情況下,都應(yīng)該使用狀態(tài)檢查方法,可讀性更好,更容易檢查錯(cuò)誤;

Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errorsuse checked exceptions for conditions from which the caller can reasonably be expected to recover.
unchecked exception: RuntimeException
, Error
通常都不需要、也不應(yīng)該catch
Use runtime exceptions to indicate programming errors. 通常用于表示程序運(yùn)行的狀態(tài)違背了前提條件,違背了API的約定
all of the unchecked throwables you implement should subclass RuntimeException

Item 59: Avoid unnecessary use of checked exceptions如果即便合理的調(diào)用了API也會遇到異常情形,并且捕獲異常之后能夠進(jìn)行一些有意義的操作,才應(yīng)該使用checked exception,其他情況下都應(yīng)該使用RuntimeException
通常,如果一個(gè)方法會拋出checked exception,都可以將其拆分為兩個(gè)方法,一個(gè)用于判斷是否會拋出異常,另一部分用于處理正常情況,如果不符合約定,就拋出RuntimeException,這樣使得API更易用,也更靈活;但是要考慮狀態(tài)檢查和執(zhí)行之間,是否可能從外部其他線程修改對象的狀態(tài);

Item 60: Favor the use of standard exceptionsIllegalArgumentException, IllegalStateException, NullPointerException, IndexOutOfBoundsException, ConcurrentModificationException, UnsupportedOperationException

Item 61: Throw exceptions appropriate to the abstraction
exception translation: higher layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction.
// Exception Translationtry { // Use lower-level abstraction to do our bidding ...} catch(LowerLevelException e) { throw new HigherLevelException(...);}

While exception translation is superior to mindless propagation of excep- tions from lower layers, it should not be overused.

Item 62: Document all exceptions thrown by each methodAlways declare checked exceptions individually, and document precisely the conditions under which each one is thrown using the Javadoc @throws tag. 不要通過聲明拋出多個(gè)異常的父類來實(shí)現(xiàn)拋出多種異常的效果。
要為每個(gè)方法可能拋出的unchecked exception寫文檔,但是不要將這些異常放到方法聲明的異常表中去。便于API使用者區(qū)分checked和unchecked exception。
如果一個(gè)類的很多方法都拋出同一個(gè)異常,那么可以將文檔放到class doc中,而不是method doc中。

Item 63: Include failure-capture information in detail messagesTo capture the failure, the detail message of an exception should contain the values of all parameters and fields that “contributed to the exception.”
良好設(shè)計(jì)的Exception類,應(yīng)該把它需要的詳細(xì)信息都作為構(gòu)造函數(shù)的參數(shù),而不是統(tǒng)一接收String參數(shù);這樣將把生成有意義的detail信息的任務(wù)集中在了Exception類本身,而不是其使用者。
checked exception可以為failure-capture information提供訪問方法,以便于使用者在程序上進(jìn)行恢復(fù)處理;雖然unchecked exception通常不會在程序中進(jìn)行恢復(fù),但是提供同樣的方法也是建議的做法。

Item 64: Strive for failure atomicityGenerally speaking, a failed method invocation should leave the object in the state that it was in prior to the invocation. 滿足此屬性的方法稱為 failure atomic。immutable對象是最簡單的實(shí)現(xiàn)方法
mutable對象要達(dá)到此效果,就需要在進(jìn)行操作前,對所有的參數(shù)、field進(jìn)行檢查
有可能無法在函數(shù)的第一部分進(jìn)行檢查,但是一定要在對對象進(jìn)行修改之前進(jìn)行檢查
還有一種不太常見的方式:函數(shù)內(nèi)部捕獲異常,異常發(fā)生之后先回退對象的狀態(tài),再把異常拋出去
還可以先創(chuàng)建一個(gè)臨時(shí)的對象,在臨時(shí)對象上進(jìn)行操作,成功后替換原對象的值

有的情況下,failure atomic是不可能的,所以也就沒必要為此做出努力了
有的情況下,為了failure atomic,會增加很多額外的開銷、復(fù)雜度,也就不太必要了
當(dāng)方法不滿足failure atomic時(shí),需要在文檔中進(jìn)行說明

Item 65: Don’t ignore exceptionsAn empty catch block defeats the purpose of exceptions
At the very least, the catch block should contain a comment explaining why it is appropriate to ignore the exception.
忽略異常,可能導(dǎo)致程序在其他不相關(guān)的地方失敗/崩潰,這時(shí)將很難找到/解決根本問題

并發(fā)
Item 66: Synchronize access to shared mutable data
synchronized
不僅是為了保證每個(gè)線程訪問/執(zhí)行時(shí),看到的都是“正常狀態(tài)”的對象(所謂正常就是沒有發(fā)生多線程同時(shí)未加同步的寫同一個(gè)對象,導(dǎo)致其狀態(tài)不一致);還能保證每個(gè)線程看到的都是最新的對象;
Java語言保證了基本類型中除了long和double的訪問都是原子性的,并發(fā)寫這些類型的數(shù)據(jù)而不進(jìn)行同步控制,也不會有問題
有人建議訪問具有原子性操作屬性的對象無需進(jìn)行同步控制,還能提升性能,純屬一派胡言
Java語言不會保證并發(fā)訪問時(shí),其他線程寫的值能立即被讀的線程感知,所以同步操作不僅僅是為了互斥訪問,也是為了保證多線程之間看到的始終是最新的值
上述問題的根本原因就是Java memory model
一個(gè)簡單、常見、易錯(cuò)的例子
如何停止后臺線程?首先不能調(diào)用Thread.stop
方法,這個(gè)方法會導(dǎo)致data corruption
常用的方法就是用一個(gè)boolean
變量,后臺線程根據(jù)其值決定是否停止,而主線程想要停止后臺線程時(shí),修改這個(gè)變量的值即可
boolean
的讀寫操作是原子性的,并發(fā)訪問不加同步,不會導(dǎo)致data corruption,但是卻無法保證主線程對變量的修改能及時(shí)被后臺線程感知,甚至無法保證能被感知
指令重排,如果done
就是個(gè)普通聲明的boolean
,以下變換在Java memory model下是允許的
while (!done) i++; //==> if (!done) while (true) i++;

可想而知,如果未進(jìn)行同步操作,后臺線程將永遠(yuǎn)不會停止
解決方法有兩種為done
的讀寫訪問都加上synchronized
,注意,讀寫都需要,否則沒有數(shù)據(jù)同步(communication)的效果;由于boolean
的讀寫訪問是原子性的,所以這里的synchronized
僅僅起數(shù)據(jù)同步的作用;
聲明done
的時(shí)候加上volatile
關(guān)鍵字,volatile
沒有互斥的作用,僅僅是起數(shù)據(jù)同步的作用,在這里正好滿足需求;這種方式性能比上一種要好一些;

使用volatile
需要格外謹(jǐn)慎,因?yàn)樗]有互斥作用,如果聲明一個(gè)volatile int
,然后對其進(jìn)行++
操作,那將會導(dǎo)致data corruption,因?yàn)?+
不是原子性的
對于這種需求,可以聲明為synchronized int
;更好的方式是使用java.util.concurrent.atomic
包下的類,安全,高效;
更根本的解決方式就是不要多線程共享mutable對象,而是共享immutable對象;甚至不要多線程共享數(shù)據(jù);
引入框架/庫時(shí),需要考慮一下它們是否會引入多線程問題
effectively immutable:對象不是真的immutable,但是對象分享出去之后,就不會再改變了;當(dāng)然這個(gè)還是很危險(xiǎn)的,因?yàn)椴]有強(qiáng)制的機(jī)制保證不會被修改;
小結(jié):多線程訪問共享變量時(shí),讀和寫都需要進(jìn)行同步操作

Item 67: Avoid excessive synchronization在同步代碼塊中,不要調(diào)用可能被重寫的方法,更不要調(diào)用使用者傳入對象的方法,因?yàn)檫@些代碼是不可控的,可能導(dǎo)致異常、死鎖、data corruption
對于Observer模式中的observer list,Java 1.5之后有一個(gè)單獨(dú)優(yōu)化的高效并發(fā)容器:CopyOnWriteArrayList
,每次寫(添加、刪除)操作都會從內(nèi)部的數(shù)組創(chuàng)建一份新的拷貝,讀(遍歷)操作時(shí)完全不用加鎖,對于讀多寫少的場景性能很好
一個(gè)總的原則是,在同步代碼塊中,執(zhí)行盡可能少的操作;如果有耗時(shí)操作,應(yīng)該在保證安全的前提下,嘗試各種手段,將其移出同步塊;
過度同步的性能影響喪失了多核CPU的并行性,獲得鎖的開銷倒是其次
任何時(shí)刻都需要保證每個(gè)CPU核心之間的數(shù)據(jù)同步,這有不小的開銷
限制了JVM的代碼優(yōu)化空間

共享數(shù)據(jù)的并發(fā)訪問,一定要保證線程安全;如果可以在類內(nèi)部,通過少量/高效的同步塊保證,就不要把整個(gè)類的任何操作都加鎖;如果做不到,那就不要進(jìn)行任何同步,把這個(gè)責(zé)任交給使用者,給他們優(yōu)化的空間,但一定要在文檔中說明;
如果static
成員可以被某些方法修改,那一定要為它們加鎖,因?yàn)檫@種情況下使用者無法保證線程安全性

Item 68: Prefer executors and tasks to threads
Executor Framework

ExecutorService executor = Executors.newSingleThreadExecutor();executor.execute(runnable);executor.shutdown();

Executors
提供了多個(gè)工廠方法,創(chuàng)建ExecutorService
,還可以直接使用ThreadPoolExecutor
,對線程池做更精細(xì)的控制
如果程序負(fù)載輕,可以使用Executors.newCachedThreadPool
,任務(wù)提交時(shí)如果沒有空閑線程,將創(chuàng)建新的線程;如果負(fù)載重,用Executors.newFixedThreadPool
更合適;
不僅不應(yīng)該自己實(shí)現(xiàn)任務(wù)隊(duì)列,甚至都應(yīng)該避免直接使用線程,而是使用Executor Framework;
任務(wù)和機(jī)制被分別抽象了,前者為Runnable
和Callable
,后者則是executor service;
java.util.Timer
也盡量不要用了,可以使用ScheduledThreadPoolExecutor
;

Item 69: Prefer concurrency utilities to wait and notify正確使用wait
和notify
有難度,而Java又提供了更高層的抽象,何樂而不用呢?
java.util.concurrent
包主要包含三塊:Executor Framework
concurrent collections
synchronizers

concurrent collections提供了標(biāo)準(zhǔn)容器的多線程高性能版本,它們內(nèi)部進(jìn)行了同步互斥操作,保證正確性;外部使用的時(shí)候,無需加鎖,否則只會導(dǎo)致性能下降;concurrent collections中的每一種實(shí)現(xiàn),可能都有性能優(yōu)化的側(cè)重點(diǎn),可能有的是多讀少寫高效,例如CopyOnWriteArrayList
,所以使用時(shí)需要了解清楚其試用場景;
除非有明確的理由,否則,優(yōu)先使用ConcurrentHashMap
,而不是Collections.synchronizedMap
或者Hashtable
;也盡量避免在使用者那端進(jìn)行同步操作;
有的concurrent collections提供了block操作接口,例如BlockingQueue
,從中取數(shù)據(jù)的時(shí)候,如果隊(duì)列為空,線程將等待,新的數(shù)據(jù)加入后,將自動喚醒等待的線程;大部分的ExecutorService
都是采用這種方式實(shí)現(xiàn)的;

Synchronizers: CountDownLatch
, Semaphore
, CyclicBarrier
, Exchanger
CountDownLatch
: 多個(gè)線程等待另外一個(gè)或多個(gè)線程完成某種工作
注意thread starvation deadlock問題
Thread.currentThread().interrupt()
idiom:異??赡軓钠渌€程拋出?用此方法回到原來的線程?
計(jì)時(shí)的話,用System.nanoTime()
而不是System.currentTimeMillis()
,前者更準(zhǔn)確,更明確

如果非要用wait
和notify
,注意以下幾點(diǎn):Always use the wait loop idiom to invoke the wait method; never invoke it outside of a loop.
wait前的條件檢查可以保證不會死鎖,wait后的檢查可以保證安全
通常情況下都應(yīng)該使用notifyAll

Item 70: Document thread safety
一個(gè)方法的聲明中加了synchronized
并不能保證它是線程安全的,并且Javadoc也不會把這個(gè)關(guān)鍵字輸出到文檔中
線程安全也分好幾個(gè)層次,文檔中應(yīng)該說明類/方法做到了何種程度上的線程安全
線程安全的分類immutable,對象創(chuàng)建后不可修改,無需進(jìn)行外部的同步操作(互斥訪問控制或許更恰當(dāng));例如:String
, Long
, BigInteger
;
unconditionally thread-safe,對象可變,但是其內(nèi)部進(jìn)行了正確的同步操作,無需外部進(jìn)行同步;例如:ConcurrentHashMap
;
conditionally thread-safe,和絕對線程安全類似,但是有些方法需要進(jìn)行外部的同步操作;例如:Collections.synchronized
返回的容器,它們的iterator使用時(shí)需要進(jìn)行同步;
not thread-safe,類自身沒有任何同步操作,需要使用者自己保證線程安全;例如:ArrayList

thread-hostile,由于類的實(shí)現(xiàn)原因,使用者無論如何也無法保證線程安全,例如未加同步的修改static成員;例如:System.runFinalizersOnExit
;

jsr-305引入了幾個(gè)注解:Immutable
, ThreadSafe
, NotThreadSafe
,對應(yīng)上述前四種情形,絕對線程安全與條件線程安全都屬ThreadSafe
,對于條件線程安全還應(yīng)在文檔中說明何種情況下是需要外部進(jìn)行同步的;
如果一個(gè)類,將它用于synchronized
的對象暴露出去了,那是很危險(xiǎn)的,通常的做法是,內(nèi)部創(chuàng)建一個(gè)Object
實(shí)例,將其用于synchronized
,但這種方式通常只適用于unconditionally thread-safe的實(shí)現(xiàn)。
// Private lock object idiom - thwarts denial-of-service attack private final Object lock = new Object(); public void foo() { synchronized(lock) { ... } }

Item 71: Use lazy initialization judiciously
don’t do it unless you need to
如果使用lazy initialization,那這個(gè)成員的訪問方法要用synchronized
修飾
靜態(tài)成員實(shí)現(xiàn)lazy initialization且希望高性能,使用lazy initialization holder class idiom,例如:
// Lazy initialization holder class idiom for static fields private static class FieldHolder { static final FieldType field = computeFieldValue(); } static FieldType getField() { return FieldHolder.field; }

實(shí)例成員要實(shí)現(xiàn)lazy initialization且希望高性能,使用double-check idiom,但是注意,double-check并非嚴(yán)格意義的線程安全,例如:
// Double-check idiom for lazy initialization of instance fields private volatile FieldType field; FieldType getField() { FieldType result = field; if (result == null) { // First check (no locking) synchronized(this) { result = field; if (result == null) // Second check (with locking) field = result = computeFieldValue(); } } return result; }

result
這個(gè)局部變量的作用是,通常情況下,field
已經(jīng)初始化過了,這時(shí)將只會對其產(chǎn)生一次讀操作,性能會有所提升

double-check idiom還有兩個(gè)變體,各有其使用場景:single-check idiom,racy single-check idiom;前者忍受多次賦值,后者忍受多次賦值且field的操作具有原子性(primitive類型且不是long和double);

Item 72: Don’t depend on the thread scheduler依賴線程調(diào)度器的正確性、性能的程序,很可能是無法移植的
好的多線程程序,同時(shí)運(yùn)行的線程數(shù)不應(yīng)該多于CPU內(nèi)核數(shù)
線程無法進(jìn)行有意義的工作時(shí),就不應(yīng)繼續(xù)運(yùn)行,忙等是不好的實(shí)現(xiàn)方式
另外一個(gè)線程(task)的工作也不能太少,否則線程切換的開銷都會大于線程執(zhí)行的時(shí)間,此時(shí)性能可想而知很低
Thread.yield
has no testable semantics. 所以不要用Thread.yield
,當(dāng)程序的有些線程因?yàn)榫€程過多而無法獲得CPU時(shí)間時(shí),應(yīng)該減少線程數(shù)。
線程優(yōu)先級是Java平臺中移植性最差的部分,所以也不要用

Item 73: Avoid thread groups如果設(shè)計(jì)的類需要處理一些邏輯上有關(guān)聯(lián)的線程,應(yīng)該考慮 thread pool executors

Serialization
Item 74: Implement Serializable judiciously實(shí)現(xiàn)Serializable
接口之后,一旦類發(fā)布出去,就不能隨意更改實(shí)現(xiàn)方式了,否則序列化-反序列化時(shí)可能失敗,這降低了靈活性
序列化-反序列化的格式也是暴露的API之一,而默認(rèn)的格式是和內(nèi)部具體實(shí)現(xiàn)細(xì)節(jié)綁定的,所以默認(rèn)格式把內(nèi)部實(shí)現(xiàn)細(xì)節(jié)也暴露出去了
自定義序列化-反序列化格式(ObjectOutputStream.putFields
, ObjectInputStream.readFields
),可以緩解上述問題,但是這又帶來了新的實(shí)現(xiàn)復(fù)雜度
serialVersionUID
問題
會增加bug、安全漏洞的可能性,因?yàn)榉葱蛄谢玫降膶ο?,其狀態(tài)是無法保證的
會增加發(fā)布新版時(shí)的測試工作
被設(shè)計(jì)于用來被繼承的類,謹(jǐn)慎實(shí)現(xiàn)Serializable
接口,同樣,設(shè)計(jì)的接口也謹(jǐn)慎繼承Serializable
接口
內(nèi)部類不應(yīng)該實(shí)現(xiàn)Serializable
接口

Item 75: Consider using a custom serialized formDo not accept the default serialized form without first considering whether it is appropriate.
The default serialized form is likely to be appropriate if an object’s physical representation is identical to its logical content.
Even if you decide that the default serialized form is appropriate, you often must provide a readObject method to ensure invariants and security.
Regardless of what serialized form you choose, declare an explicit serial version UID in every serializable class you write.

Item 76: Write readObject
methods defensivelyreadObject
方法的功效和public的構(gòu)造函數(shù)一樣
反序列化的時(shí)候,readObject
如果不進(jìn)行深拷貝、以及數(shù)據(jù)合法性驗(yàn)證,就會導(dǎo)致生成的對象數(shù)據(jù)非法,同時(shí),也有可能獲得反序列化后對象內(nèi)部成員的引用(rogue object reference attacks)
不要使用writeUnshared
和readUnshared
方法,它們并不安全
前文應(yīng)該提到過,非final類,構(gòu)造函數(shù)以及readObject
方法中,不能調(diào)用可重載的方法

Item 77: For instance control, prefer enum types to readResolveif you depend on readResolve for instance control, all instance fields with object reference types must be declared transient. 否則可能會無法達(dá)到實(shí)例控制的目的。
The accessibility of readResolve is significant.final類,應(yīng)該置為private

Item 78: Consider serialization proxies instead of serialized instances
為需要實(shí)現(xiàn)Serializable
的類添加一個(gè)內(nèi)部類,它的構(gòu)造函數(shù)接收外部類的實(shí)例,并將其field拷貝到自身的field,并且實(shí)現(xiàn)readResolve
方法,創(chuàng)建外部類實(shí)例,創(chuàng)建方法可以是構(gòu)造函數(shù)、static factory函數(shù),在其中就可以進(jìn)行實(shí)例控制了
// Serialization proxy for Period class private static class SerializationProxy implements Serializable { private final Date start; private final Date end; SerializationProxy(Period p) { this.start = p.start; this.end = p.end; } // readResolve method for Period.SerializationProxy private Object readResolve() { return new Period(start, end); // Uses public constructor } private static final long serialVersionUID = 234098243823485285L; // Any number will do (Item 75) }

外部類實(shí)現(xiàn)一個(gè)writeReplace
方法
// writeReplace method for the serialization proxy pattern private Object writeReplace() { return new SerializationProxy(this); }

外部類實(shí)現(xiàn)readObject
方法,并在其中拋出異常
// readObject method for the serialization proxy pattern private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); }

技術(shù)成長路漫漫,基礎(chǔ)需要夯實(shí),前沿需要緊跟,術(shù)業(yè)亦需專攻,日后仍需孜孜不倦,持續(xù)前進(jìn)。
NEVER STOP

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 《Effective Java》筆記(上) 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法,而不是構(gòu)造...
    OCNYang閱讀 2,788評論 2 17
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,822評論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • 第74期:小程序的風(fēng)口及內(nèi)容創(chuàng)業(yè)的技術(shù)解決方案 根據(jù)音頻整理 9月30號新榜正式發(fā)布了新榜的小程序。我看了一下它的...
    李宜璞閱讀 287評論 0 0
  • 結(jié)婚2周年紀(jì)念日。你在那屋我在這屋。很近卻很遠(yuǎn)。不喜歡看你看我的那張臉。感覺我好像特別礙眼。妨礙你了一樣。是啊。結(jié)...
    需要勇氣閱讀 400評論 0 1

友情鏈接更多精彩內(nèi)容