|
《Effective JavaJava》名著,必讀。如果能嚴(yán)格遵從本文的原則,以編寫(xiě)API的質(zhì)量來(lái)苛求自己的代碼,會(huì)大大提升編碼素質(zhì)。 以下內(nèi)容只記錄了我自己整理的東西,還是建議讀原文。為了聚焦知識(shí)點(diǎn),一些說(shuō)明故意忽略掉了。相當(dāng)于是一篇摘要。 1、考慮用靜態(tài)工廠方法替代構(gòu)造函數(shù) 例子:
優(yōu)勢(shì):
下面針對(duì)三個(gè)優(yōu)勢(shì)進(jìn)行一些解讀。 可讀性高new Point(x,y)和Point.at(x,y)、Point.origin()。構(gòu)造函數(shù)只能看出兩個(gè)參數(shù),不知其意,后者更易理解。 性能在某些情況下,可以事先進(jìn)行實(shí)例化一些對(duì)象,調(diào)用時(shí)直接調(diào)用即可,不需要進(jìn)行改變。比如,Boolean。
靈活性高可根據(jù)具體情況,返回子類。相當(dāng)于更強(qiáng)大的工廠。直接從父類獲取到子類。尤其適用于工具類(提供各種API)。例子:Collections。
2、多個(gè)構(gòu)造函數(shù)時(shí),考慮使用構(gòu)造器尤其在進(jìn)行Android開(kāi)發(fā)時(shí),會(huì)碰到這種情況。通常是一個(gè)對(duì)象,具有多個(gè)成員變量可能需要初始化,常規(guī)方法,需要提供大量構(gòu)造函數(shù)。例如:
有多種樣式的警告框,為了調(diào)用方便,必須提供多個(gè)構(gòu)造函數(shù)。否則用戶在調(diào)用時(shí),只能使用完整構(gòu)造函數(shù),容易犯錯(cuò)且無(wú)法進(jìn)行閱讀。極不靈活。如果采用另外一種方式,則可以解決,但會(huì)花費(fèi)很多經(jīng)歷處理并發(fā)的情況:
調(diào)用時(shí),通過(guò)調(diào)用各個(gè)參數(shù)的set方法進(jìn)行設(shè)置。問(wèn)題來(lái)了:
在Android中,大量的控件都使用了構(gòu)造器Builder。
于是,可以根據(jù)相應(yīng)需求,進(jìn)行相應(yīng)設(shè)置,并在AlertDialog真正構(gòu)造時(shí),進(jìn)行參數(shù)校驗(yàn)。就像這樣:
上述例子,會(huì)成功拋出異常。 3、用私有化構(gòu)造器或者枚舉型強(qiáng)化Singleton。Singleton指最多會(huì)被實(shí)例化一次的類。通常情況下,以前的做法是沒(méi)有問(wèn)題的。但是在某些高級(jí)情況,通過(guò)使用反射的相關(guān)知識(shí)訪問(wèn)private的構(gòu)造函數(shù),破壞Singleton。
另一種情況,在序列化的過(guò)程中,反序列化得到的對(duì)象已經(jīng)不再是以前的對(duì)象(破壞了Singleton),這種情況下,可以通過(guò)單元素枚舉型處理。
4、通過(guò)私有化構(gòu)造器強(qiáng)化不可實(shí)例化的能力有一些工具類,僅僅是提供一些能力,自己本身不具備任何屬性,所以,不適合提供構(gòu)造函數(shù)。然而,缺失構(gòu)造函數(shù)編譯器會(huì)自動(dòng)添加上一個(gè)無(wú)參的構(gòu)造器。所以,需要提供一個(gè)私有化的構(gòu)造函數(shù)。為了防止在類內(nèi)部誤用,再加上一個(gè)保護(hù)措施和注釋。
弊端是無(wú)法對(duì)該類進(jìn)行繼承(子類會(huì)調(diào)用super())。 5、避免創(chuàng)建不必要的對(duì)象
6、消除過(guò)期的對(duì)象引用以下三種情況可能會(huì)造成內(nèi)存泄露:
自己管理的內(nèi)存對(duì)于自己管理的內(nèi)存要小心,比如:
彈出的對(duì)象不再有效,但JVM不知道,所以會(huì)一直保持該對(duì)象,造成內(nèi)存泄露。 解決:
緩存緩存的對(duì)象容易被程序員遺忘,需要設(shè)置機(jī)制來(lái)維護(hù)緩存,例如不定期回收不再使用的緩存(使用定時(shí)器)。某些情況下,使用WeakHashMap可以達(dá)到緩存回收的功效。注,只有緩存依賴于外部環(huán)境,而不是依賴于值時(shí),WeakHashMap才有效。 監(jiān)聽(tīng)或回調(diào)使用監(jiān)聽(tīng)和回調(diào)要記住取消注冊(cè)。確?;厥盏淖詈玫膶?shí)現(xiàn)是使用弱引用(weak reference),例如,只將他們保存成WeakHashMap的鍵。 7、避免顯示調(diào)用GCJava的GC有強(qiáng)大的回收機(jī)制,可以簡(jiǎn)單的記?。翰灰@示調(diào)用finalizer。可以這樣理解: jvm是針對(duì)具體的硬件設(shè)計(jì)的,然而程序卻不是針對(duì)具體硬件設(shè)計(jì)的,所以,java代碼無(wú)法很好的解決gc問(wèn)題(因?yàn)樗哂衅脚_(tái)差異化)。另外,finalizer的性能開(kāi)銷也非常大,從這個(gè)角度上考慮也不應(yīng)該使用它。 8、覆蓋equals方法請(qǐng)遵守通用約定
9、覆蓋equals方法時(shí)總要覆蓋hashCode為了保證基于散列的集合使用該類(HashMap、HashSet、HashTable),同時(shí),也是Object.hashCode的通用約定,覆蓋equals方法時(shí),必須覆蓋hashCode。 10、始終覆蓋toStringObject的toString方法的通用約定是該對(duì)象的描述。注意覆蓋時(shí),如果有格式,請(qǐng)備注或者嚴(yán)格按照格式返回。 11、謹(jǐn)慎覆蓋clone12、考慮實(shí)現(xiàn)Comparable接口13、使類和成員的可訪問(wèn)性最小化目的是解耦。簡(jiǎn)單來(lái)講,使用修飾符的優(yōu)先級(jí)從大到小,private>protected>default(缺省)>public。如果在設(shè)計(jì)之初,設(shè)計(jì)為private修飾符后,在之后的編碼過(guò)程如果不得不擴(kuò)大其作用于,應(yīng)該先檢查是否設(shè)計(jì)的確如此。 子類覆蓋超類,不允許訪問(wèn)級(jí)別低于超類的訪問(wèn)級(jí)別。(超類的protected,子類覆蓋后不能改為default)。 成員變量決不允許是公有的。一旦設(shè)置為公有,則放棄了對(duì)他處理的能力。這種類并不是線程安全的。即使是final的,也不允許。除非希望通過(guò)public static final來(lái)暴露常量。成員變量總是需要使用setter和getter來(lái)維護(hù)。有一個(gè)例外:長(zhǎng)度非零的數(shù)組。這是安全漏洞的一個(gè)根源。
改進(jìn):
另一種:
14、在公有類中使用訪問(wèn)方法而非公有成員變量(類似13)15、使可變性最小化16、復(fù)合優(yōu)先于繼承繼承有利于代碼復(fù)用,但是盡可能不要進(jìn)行跨包的繼承。包內(nèi)的繼承是優(yōu)秀的設(shè)計(jì)方式,一個(gè)包里的文件處在同一個(gè)程序員的控制之下。但是繼承有其局限性:子類依賴于超類。超類一旦發(fā)生更改,將可能破壞子類。并且,如果超類是有缺陷的,子類也會(huì)得“遺傳病”。 復(fù)合,即不擴(kuò)展已有的類,而是在的類中新增一個(gè)現(xiàn)有類的。相當(dāng)于現(xiàn)有類作為一個(gè)組建存在于新類中。如此,將只會(huì)用到需要用到的東西,而不表現(xiàn)現(xiàn)有類所有的方法和成員變量。新類也可以稱為“包裝類”,也就是設(shè)計(jì)模式中的Decorate模式。 17、要么就為繼承而設(shè)計(jì),并提供文檔說(shuō)明,要么就禁止繼承18、接口優(yōu)于抽象類19、接口只用于定義類型20、類層次優(yōu)先于標(biāo)簽類21、用函數(shù)對(duì)象表示策略函數(shù)參數(shù)可以傳入類似listener的對(duì)象,目的是使用listener中的方法。如果使用匿名的參數(shù),每一次調(diào)用會(huì)創(chuàng)建新的對(duì)象??梢詫istener聲明為成員變量,每次都復(fù)用同一個(gè)對(duì)象,并且可以使用靜態(tài)域(static變量)。比如String類的CASE_INSENSITIVE_ORDER域。 關(guān)注公眾號(hào)【程序員白楠楠】獲取2020年末總結(jié)面試資料一套! 考慮靜態(tài)類成員 嵌套類的目的應(yīng)該只是為了他的外圍類提供服務(wù),如果以后還可能用于其他環(huán)境中,則應(yīng)該設(shè)計(jì)為頂層類。靜態(tài)類相當(dāng)于一個(gè)普通的外部類,只是恰好聲明在了一個(gè)類內(nèi)部。通常的用戶是:Calculator.Operation.PLUS等。和普通類的區(qū)別只是,在PLUS前,有了2個(gè)前綴,來(lái)表明其含義。而非靜態(tài)類必須存在于外部類對(duì)象中。不要手動(dòng)在外部創(chuàng)建一個(gè)內(nèi)部非靜態(tài)類對(duì)象,創(chuàng)建的過(guò)程是:instance.New MemberClass()。這非常奇怪。 如果成員類不需要訪問(wèn)外圍類,則需要添加static,是他成為靜態(tài)成員類,否則每個(gè)實(shí)例都將包含一個(gè)額外指向外圍對(duì)象的引用。將會(huì)影響垃圾回收機(jī)制。 23、應(yīng)指定泛型的具體類型,而不是直接使用原生類型。例如,應(yīng)該指定 24、消除非首檢警告在使用IDE進(jìn)行編碼時(shí),強(qiáng)大的IDE都會(huì)在你編碼過(guò)程中提示warning,需要盡可能的消除warning,至少,應(yīng)該小心這些warning。慎用SuppresWarning,如果IDE提示你可以通過(guò)添加該注解解決掉warning,請(qǐng)不要那么做。如果實(shí)在要使用,請(qǐng)?zhí)砑幼⑨屨f(shuō)明原因。 25、列表優(yōu)先于數(shù)組類比泛型,數(shù)組是有一定缺陷的。List和List是沒(méi)有關(guān)系的,而Sub[]是Super[]的子類。
從代碼中可以看到,使用泛型,會(huì)提前發(fā)現(xiàn)錯(cuò)誤。 26、優(yōu)先考慮泛型27、優(yōu)先考慮泛型方法28、利用有限制通配符來(lái)提升API的靈活性PECS,producer-extends,consumer-super。
所有comparable和comparator都是消費(fèi)者(Consumer)。 29、優(yōu)先考慮類型安全的異構(gòu)容器30、用enum代替int常量
枚舉型在java中非常強(qiáng)大,當(dāng)需要一組固定常量時(shí),使用enum比int好很多。比如代碼可讀性,安全性等。 31、enum用實(shí)例域代替序數(shù)
永遠(yuǎn)不要像第一種的方式,利用序數(shù)訪問(wèn)enum,需要在構(gòu)造函數(shù)中使用參數(shù)來(lái)初始化。 32、用EnumSet代替位域
以上叫做位圖法,但是有更好的方案來(lái)傳遞多組常量——EnumSet。
33、用EnumMap代替序數(shù)索引任何時(shí)候都不要使用enum的ordinal()方法。 34、用接口模擬可伸縮的枚舉35、注解優(yōu)先于命名模式36、堅(jiān)持使用Override注解37、檢查參數(shù)的有效性公有方法檢查參數(shù),參數(shù)異常需要跑出Exception。私有方法利用斷言assertion檢查參數(shù)。 38、必要時(shí)進(jìn)行保護(hù)性拷貝假設(shè)類的客戶端會(huì)盡其所能來(lái)破壞這個(gè)類的約束條件,因此你必須保護(hù)性的設(shè)計(jì)程序。以下是一個(gè)不可變類的設(shè)計(jì)。
注意:保護(hù)性拷貝是在檢查參數(shù)之前進(jìn)行的,防止多線程的影響。不要使用clone方法進(jìn)行保護(hù)性拷貝。 以上方法防御了傳入?yún)?shù)的修改,但是對(duì)于get方法獲取到的對(duì)象,仍然可以被修改,通過(guò)以下方法可以防止這種攻擊。
39、謹(jǐn)慎設(shè)計(jì)方法簽名40、慎用重載41、慎用可變參數(shù)42、返回0長(zhǎng)度的數(shù)組或者集合,而不是nullnull一般用于表示沒(méi)有被初始化或處理,如果方法返回了null,則需要在上層做更多的處理,以防止NPE。 43、為所有導(dǎo)出的API元素編寫(xiě)文檔注釋正確的javadoc文檔,需要每個(gè)被導(dǎo)出的類、接口、構(gòu)造器、方法和域之前增加文檔注釋。注釋?xiě)?yīng)該是對(duì)實(shí)現(xiàn)透明的,只需要簡(jiǎn)潔的描述它和客戶端之間的約定。并且,還應(yīng)該附上該方法的副作用。 44、將局部變量的作用域最小化45、for-each優(yōu)先于for循環(huán)for-each規(guī)避掉了for循環(huán)的index變量的引用,通常來(lái)說(shuō)它是不必要的——會(huì)增加引入錯(cuò)誤的風(fēng)險(xiǎn),并且風(fēng)險(xiǎn)一旦發(fā)生,很難被發(fā)現(xiàn)。不過(guò)有三種情況下,無(wú)法使用for-each(注:在jdk1.8中已經(jīng)很好的解決了這些問(wèn)題)。
46、如果需要精確的答案,請(qǐng)避免使用float和doublefloat和double是執(zhí)行的二進(jìn)制浮點(diǎn)運(yùn)算,目的是在廣泛數(shù)值范圍上使用精確的快速近似計(jì)算而設(shè)計(jì)的。然而他們并沒(méi)有提供完全精確的計(jì)算(實(shí)際應(yīng)用中,經(jīng)常會(huì)碰到出現(xiàn)x.99999等結(jié)果)。尤其是,在進(jìn)行貨幣計(jì)算時(shí),他們并不適用。比如:
得到的結(jié)果將是:0.610000000001。 為了解決這個(gè)問(wèn)題,需要使用BigDecimal。然而這也有一些問(wèn)題,相對(duì)于普通的運(yùn)算,它顯得更加麻煩,而且也更慢。通常來(lái)說(shuō)后一個(gè)缺點(diǎn)可以忽略,但是前者可能會(huì)讓人很不舒服。有一種做法是將需要處理的數(shù)值*10(或更多),使用int進(jìn)行計(jì)算,不過(guò)需要你自己處理四舍五入等操作。 47、基本類型優(yōu)先于裝箱基本類型
48、如果有更精確的類型,請(qǐng)避免使用字符串
49、當(dāng)心字符串連接的性能操作符“+”可以將多個(gè)字符串進(jìn)行連接。但是在大規(guī)模使用“+”的情況下,連接n個(gè)字符串的開(kāi)銷是n的平房級(jí)時(shí)間。這是由于字符串的不可變性導(dǎo)致的。在這種情況下請(qǐng)使用StringBuilder進(jìn)行連接。 50、通過(guò)接口引用對(duì)象最后,小編總結(jié)了2020面試題,這份面試題的包含的模塊分為19個(gè)模塊,分別是: Java 基礎(chǔ)、容器、多線程、反射、對(duì)象拷貝、Java Web 、異常、網(wǎng)絡(luò)、設(shè)計(jì)模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM 。 關(guān)注我的公眾號(hào):程序員白楠楠,獲取上述資料。 |
|
|