Effective java 讀書筆記(2)
- 2021 年 11 月 7 日
- 筆記
- Effective java讀書筆記
第四條:通過私有構造器強化不可實例化的能力
有時可能需要編寫只包含靜態方法和靜態域的類,這樣的工具類不希望被實例化,因為實例化對它來說沒有意義。
然而,在缺少顯式構造器的情況下,系統會自動提供一個預設構造,但這種類又不能設計為抽象類,因為它不希望被繼承,也不希望它的子類能實例化。
所以,可以為其提供一個顯式的私有構造器,保證不能從類的外部調用構造,程式碼如下:
public class UtilityClass{ private UtilityClass(){ throw new AssertionError(); //AssertionError可以避免不小心在類內調用構造
} ...... }
這種習慣做法也有副作用,它使得一個類不能被子類化。
第五條:優先考慮依賴注入來引用資源
第六條:避免創建不必要的對象
當你應該重用現有對象的時候,請不要創建新的對象。
一般來說,最好能重用單個對象,而不是在每次需要的時候就創建一個新對象,如果對象是不可變的(詳見17條),它就始終可以被重用。
反面例子如下:
String s = new String("wuhu");
該語句每次執行的時候都創建一個新的String實例,但這是不必要的,若該語句處於循環中,就會創造出成千上萬不必要的String實例。
所以,正確的應該這樣做:
String s = "wuhu";
這個版本只用了一個String實例,而且可以保證,對於所有在同一台虛擬機中運行的程式碼,只要字元串相同,該對象就會被重用。
對於同時提供了靜態工廠方法和構造器的不可變類,通常優先使用靜態工廠方法而不是構造器,以避免創建不必要的對象。例如,靜態工廠方法Boolean valueOf(String)幾乎總是優先於構造器Boolean(String)(該構造在java9中已被廢棄)。構造器每次調用都會創建一個對象,而靜態工廠方法不會。除了重用不可變的對象之外,也可以重用那些已知不會被修改的可變對象。
靜態工廠方法詳見此處關於 Java 的靜態工廠方法,看這一篇就夠了! – 簡書 (jianshu.com)
有些對象創建成本很高,如果需要重複使用,建議將其快取下來重用。如以下實例:
static boolean isRomanNumeral(String s){ return s,matches(....); //此處為正則表達式 }
String.matches方法雖然最易於查詢一個字元串是否與正則表達式相匹配,但並不適合在注重性能的情形下重複使用。原因在於,它在內部為正則表達式創建了一個Pattern實例,卻只用了一次,之後就可以進行垃圾回收了。創建Pattern實例的成本很高,因為需要將正則表達式編譯為一個有限狀態機。
為了提升性能,應顯式的將正則表達式編譯成一個Pattern實例(不可變),讓它成為類初始化的一部分,並將其快取起來,每當調用isRomanNumeral方法時就重用同一個實例,如下:
public class RomanNumerals{ private static final Pattern ROMAN = Pattern.compile(...); static boolean isRomanNumeral(String s){ return ROMAN.matcher(s).matches(); } }
如果一個對象是不變的,那麼它顯然能夠被安全的重用,但其他有些情形則並不這麼明顯。考慮適配器的情形,有時也叫做視圖。
適配器模式詳見適配器模式 | 菜鳥教程 (runoob.com)
例如,Map介面的keyset方法返回Map對象的Set視圖,其中包含該Map中所有的鍵,對於給定的Map對象,每次調用keySet實際上都返回同樣的Set實例,雖然返回的Set實例一般是可改變的,但是所有返回的對象在功能上都是等同的:其中一個返回對象發生變化時,所有其他的返回對象也要發生變化,因為它們是由同一個Map實例支撐的。
另一種創建多餘對象的方法,稱為自動裝箱。請看如下程式碼:
private static long sum(){ Long sum = 0L; for(long i = 0; i <= Integer.MAX_VALUE; i++) sum += i; return sum; }
這段程式算出的答案是正確的,但是比實際情況要更慢一點,因為變數sum被聲明為Long而不是long,意味著每次先Long sum中增加一個long時都要構造一個實例,所以:要優先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱。