Effective Java這本書(shū)的意義在于提供最佳實(shí)踐,而所謂的最佳實(shí)踐又并非時(shí)時(shí)刻刻都需要這么做,所以我們需要引入問(wèn)題場(chǎng)景。關(guān)于什么樣的問(wèn)題用什么的方式來(lái)解決,重要的是思考,其次是模仿。
雖然說(shuō)是最佳實(shí)踐,但是有時(shí)候又有些局限,局限于某個(gè)Java版本,以及某些Java的設(shè)計(jì),還有一些今天看來(lái)已經(jīng)有些過(guò)時(shí)。
這本書(shū)寫(xiě)于Java1.6的時(shí)代,雖然1.6是一個(gè)跨時(shí)代的版本,但是有些內(nèi)容在今天來(lái)看已經(jīng)有些過(guò)時(shí)了,這邊文章主要在于梳理這些已經(jīng)過(guò)時(shí)的東西,以及溫習(xí)一下這本書(shū)所教所講。
僅涉及了部分章節(jié),有部分疏漏。
例如靜態(tài)工廠模式提到,它可以使原本冗余的類(lèi)型聲明變得簡(jiǎn)潔。
Map<String, List<String>> m = new HashMap<String, List<String>>();
實(shí)際上,在Java7已經(jīng)支持了這種類(lèi)型推導(dǎo),并且支持聲明缺省,寫(xiě)成如下樣子。
Map<String, List<String>> m = new HashMap<>();
再比如構(gòu)造器模式,解決的問(wèn)題場(chǎng)景很明確,那就是一個(gè)類(lèi)可能有多個(gè)多種構(gòu)造參數(shù),比較常見(jiàn)的做法就是空的構(gòu)造函數(shù),通過(guò)setter設(shè)置需要的參數(shù),這就是我們說(shuō)的JavaBean模式。而這種模式帶來(lái)的問(wèn)題是,可能存在不一致的風(fēng)險(xiǎn),需要比較大的成本來(lái)保持一致性,不過(guò)大多數(shù)情況這種構(gòu)造初始化不會(huì)很少會(huì)用于并發(fā)的場(chǎng)景,所以這里也不再討論線程安全的問(wèn)題。
作者借線程安全引出了Builder構(gòu)造器模式,我認(rèn)為這個(gè)模式的優(yōu)點(diǎn)主要是兩點(diǎn):
1. 便于可選參數(shù)構(gòu)造的構(gòu)造
2. 便于校驗(yàn)&設(shè)置參數(shù)的默認(rèn)值
3. 可以將生成的目標(biāo)類(lèi)設(shè)置為不可變對(duì)象,并且便于閱讀
工具類(lèi)在私有構(gòu)造方法中拋出異常,來(lái)避免工具類(lèi)實(shí)例化,雖然是一個(gè)技巧,然而工具類(lèi)的方法均是static的,在現(xiàn)在的編輯器比如IDEA中,實(shí)例化一個(gè)類(lèi)并調(diào)用它的靜態(tài)方法,會(huì)被編輯器提示沒(méi)必要的實(shí)例化,所以這個(gè)模式今天來(lái)看也不是特別必要。而且像工具類(lèi),除了系統(tǒng)提供的Arrays這種,大多數(shù)的命名風(fēng)格是StringUtils這個(gè)樣子,已經(jīng)逐漸成為一種默契,我們也不會(huì)去實(shí)例化這種對(duì)象。
如何不讓一個(gè)類(lèi)可以被實(shí)例化:
將構(gòu)造方法聲明成私有方法,并throw 一個(gè)assertionError(), 當(dāng)然這個(gè)throw不是必須的。
創(chuàng)建對(duì)象依然是有成本的,盡可能復(fù)用已有的對(duì)象,避免沒(méi)有必要的裝箱。
但是這又不是絕對(duì)的,再一次說(shuō)明了,場(chǎng)景決定設(shè)計(jì),這里對(duì)比保護(hù)性拷貝提到反駁這個(gè)觀點(diǎn)的一個(gè)經(jīng)典場(chǎng)景。
private final Date time;
public Date time() {
? ? return this.time;
}
盡管time是final,保證了time的不可變,但是不能保證time的內(nèi)置參數(shù)不可變,例如依然可以執(zhí)行
time().setYear(233);
這樣的操作會(huì)破壞time的原始值,如果這里設(shè)置保護(hù)性拷貝。
publi Date time() {
? ? return new Date(this.time.getTime());
}
這樣無(wú)論如何修改返回的Date對(duì)象,都不會(huì)影響到最原始的time對(duì)象,暴露私有變量需要考慮這樣的安全性。
網(wǎng)上流傳了一張比如Java垃圾回收的圖,如下

盡管Java有GC,但是依然會(huì)有一些內(nèi)存泄漏的隱患,其實(shí)關(guān)注對(duì)應(yīng)的場(chǎng)景即可。
1. 類(lèi)自己管理內(nèi)存,那么可能會(huì)有內(nèi)存泄漏的風(fēng)險(xiǎn),比如Stack類(lèi)彈出的元素,依然占據(jù)著引用,需要手動(dòng)清理。
2. 謹(jǐn)慎緩存中的對(duì)象。
3. 監(jiān)聽(tīng)器與回調(diào)。
需要補(bǔ)充的是,在一些場(chǎng)景下,大量創(chuàng)建對(duì)象也是內(nèi)存消耗的罪魁禍?zhǔn)?,由于不合理的?chuàng)建對(duì)象導(dǎo)致內(nèi)存瘋漲,導(dǎo)致頻繁CMS或者fullgc屢見(jiàn)不鮮。
覆蓋equals同時(shí)要覆蓋hashcode,這個(gè)很好理解,大多數(shù)集合類(lèi)是依據(jù)hashcode來(lái)做對(duì)象區(qū)分的。
關(guān)于Comparable方法的實(shí)現(xiàn),在如今的Java8已經(jīng)不需要去實(shí)現(xiàn)匿名內(nèi)部類(lèi)了,可以直接使用方法引用或者lambda表達(dá)式來(lái)解決這樣的問(wèn)題。
lambda表達(dá)式:
list.sort((pre, last) -> pre.valueOf().compareTo(last.valueOf()));
引用的方式:
在對(duì)象里實(shí)現(xiàn)一個(gè)靜態(tài)對(duì)比的方式,假設(shè)類(lèi)叫MyObject,方法叫compareByNum
list.sort(MyObject::compareByNum);
在討論可變性的問(wèn)題的時(shí)候,討論了一種編程的做法。
函數(shù)式編程functional,與之對(duì)應(yīng)的還有,過(guò)程式編程(procedural),命令式編程(imperative),聲明式編程(declarative)。
函數(shù)式編程通過(guò)函數(shù)調(diào)用返回新的實(shí)例,而不改變?cè)袑?shí)例。
過(guò)程式編程or命令式編程,其實(shí)是一種東西,關(guān)注的是如何做,而不是做什么。
聲明式在于,做什么,而非怎么做,比如IoC
函數(shù)式和聲明式不是一個(gè)層面上的東西,函數(shù)式更關(guān)注的是處理數(shù)據(jù)的方式。
復(fù)合和接口的轉(zhuǎn)發(fā)(復(fù)合模式-包裝類(lèi))
這里的一個(gè)思想是說(shuō),繼承與封裝是矛盾的,一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)進(jìn)行了繼承,那么就有可能受到他的改動(dòng)產(chǎn)生的影響。
將現(xiàn)有的類(lèi)作為新類(lèi)的一個(gè)組件(成員變量),心累中的每個(gè)實(shí)例都可以調(diào)用被包含的現(xiàn)有類(lèi)的方法。
通過(guò)這樣轉(zhuǎn)發(fā)的形式,使新類(lèi)不受現(xiàn)有類(lèi)的實(shí)現(xiàn)細(xì)節(jié)影響。
雖然繼承非常好用,但是繼承可能帶來(lái)的問(wèn)題是非常多的。
構(gòu)造器不能去調(diào)用可被覆蓋的方法,這是有極大風(fēng)險(xiǎn)的。
是否考慮使用繼承,應(yīng)該充分考慮可繼承的方法可以自用性,方法之間相互調(diào)用,在繼承的時(shí)候會(huì)存在各種各樣的風(fēng)險(xiǎn)。
如何拒絕繼承?
1. 把這個(gè)類(lèi)聲明稱(chēng)final類(lèi)型
2. 將構(gòu)造器變成私有的,使用公有的靜態(tài)工廠來(lái)替代構(gòu)造器。
接口在Java8引入了接口的default實(shí)現(xiàn),由于接口是可以多繼承的,那么一個(gè)新的問(wèn)題就是多繼承。Java8在處理多繼承是通過(guò)顯式指定繼承某個(gè)父接口來(lái)決定的。
21條提到的用函數(shù)對(duì)象表示策略,使用內(nèi)部靜態(tài)類(lèi)與靜態(tài)final不可變成員實(shí)現(xiàn)了函數(shù)指針,在Java8中可以直接使用lambda做這個(gè)事情。
通過(guò)這樣的方式可以實(shí)現(xiàn)策略模式,通過(guò)聲明一個(gè)抽象的策略接口,為每個(gè)具體策略實(shí)現(xiàn)對(duì)應(yīng)的接口類(lèi)。
Java中有四種嵌套類(lèi)定義:
1. 靜態(tài)成員類(lèi) > 和普通的類(lèi)沒(méi)區(qū)別,主要用共有類(lèi)的輔助類(lèi)。
2. 非靜態(tài)成員類(lèi) > 與外圍類(lèi)的實(shí)例關(guān)聯(lián),可以調(diào)用外圍類(lèi)的方法,不能獨(dú)立創(chuàng)建。
3. 匿名類(lèi) > 匿名的Comparator實(shí)例用來(lái)給Arrays.sort()使用,一般非常短。
4. 局部類(lèi) > 基本不用,聲明變量的地方即可聲明局部類(lèi)。
泛型的意義:
泛型不代表沒(méi)有類(lèi)型,顯式指定泛型的聲明,可以保證泛型受檢。
RuntimeError
Object[] objectArray = new Long[1]; ?
compile error
List<Object> ol = new ArrayList<Long>();?
Java的泛型會(huì)在運(yùn)行時(shí)進(jìn)行類(lèi)型擦除,泛型不是沒(méi)有類(lèi)型。但是Java的泛型,在編譯時(shí)還存留類(lèi)型信息,在運(yùn)行時(shí)已經(jīng)擦除了這種信息(主要為了保證使用泛型的代碼和沒(méi)有使用泛型的代碼互用)。
枚舉類(lèi)型需要這樣的一個(gè)場(chǎng)景,比如加減乘除進(jìn)行枚舉設(shè)計(jì),但是他們又是不同的行為。
可以定義一個(gè)抽象的apply方法,強(qiáng)制每個(gè)枚舉成員去覆蓋實(shí)現(xiàn),代碼如下:

其中abstract的抽象方法,也可以使enum通過(guò)繼承的方式來(lái)拓展。