這篇博客是Java經典書籍《Effective Java(第二版)》的讀書筆記,此書共有78條關於編寫高質量Java代碼的建議,我會試著逐一對其進行更為通俗易懂地講解,故此篇博客的更新大約會持續1個月左右。 第1條:考慮用靜態工廠方法代替構造器 通常情況下我們會利用類的構造器對其進行實例化,這似乎毫 ...
這篇博客是Java經典書籍《Effective Java(第二版)》的讀書筆記,此書共有78條關於編寫高質量Java代碼的建議,我會試著逐一對其進行更為通俗易懂地講解,故此篇博客的更新大約會持續1個月左右。
第1條:考慮用靜態工廠方法代替構造器
通常情況下我們會利用類的構造器對其進行實例化,這似乎毫無疑問。但“靜態工廠方法”也需要引起我們的高度註意。
什麼是“靜態工廠方法”?這不同於設計模式中的工廠方法,我們可以理解它為“在一個類中用一個靜態方法來返回這個類的實例”,例如:
public static People getInstance() { return new People(); }
它是一個“方法”,那麼它不同於構造器,它可以隨意修改方法名,這就帶來第一個優點——有名稱。有時一個類的構造器往往不止一個,而它們的名稱都是相同的,不同的僅僅是參數,如果不同參數帶來不同的含義這樣對於調用方來說除了註釋很難理解它們有什麼不同的含義。例如BigInteger(int, int, Random)返回一個素數,但調用者很難理解API設計者所要想表達的意思,如果此時有BigInteger.probablePrime靜態工廠方法,則能一目瞭然的清楚API設計者所要想表達的含義。舉一個JDK的例子:Executors類,在這個類中有newFixedThread、newSingleThreadExecutor、newCachedThreadPool等靜態方法,因為它們有“名字”,所有就較為清晰的明白API的含義。
《Effective Java》中所提到的靜態工廠方法第二個優點在於不用重覆創建一個對象,實際上也就是勤載入或者稱為餓漢式的單例模式。例如:
public class Instance() { private static Instance instance = new Instance(); private Instance(){} public static Instance getInstance() { return instance; } }
靜態工廠方法的第三個優點,可以返回原返回類型的任何子類型的。這句話初看不好理解,舉個JDK中的例子:Collections類。
List list = Collections.synchronizedList(new ArrayList())
這個例子就說明瞭可以返回原返回類型的任何子類型的對象。
關於靜態工廠方法的第四個優點,在創建參數化類型實例的時候,它們使代碼變得更加簡潔,書中舉了一個例子:
Map<String, List<String>> m = new HashMap<String, List<String>>(); //這會顯得很繁瑣
給集合類提供靜態工廠方法後:
public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); }
但是實際上從JDK7(包括JDK7)之後的集合類可以用以下簡潔的代碼代替:
Map<String, List<String>> m = new HashMap<>();
靜態工廠方法也有兩個缺點:一是公有的靜態方法所返回的非公有類不能被實例化,也就是說Collections.synchronizedList返回的SynchronizedList不能被實例化;二是查找API比較麻煩,它們不像普通的類有構造器在API中標識出來,而是和其他普通靜態方法一樣,鑒於此,書中提到了幾種慣用名稱:
valueOf
of
getInstance
newInstance
getType
newType
第2條:遇到多個構造器參數時要考慮用構建器
你是否寫過下麵類似的代碼:
public void Student() { /*必填*/ private String name; private int age; /*選填*/ private String sex; private String grade; public Student(String name, String sex) { this(name, sex, 0); } public Student(String name, String sex, int age) { this(name, sex, age, “”); } public Student(String name, String sex, int age, String grade) { this.name = name; this.sex = sex; this.age = age; this.grade = grade; } }
當我想實例化一個名字叫“kevin”,性別男,但是不寫年齡,只有年級“1年級”,這個時候代碼就:不得不要為年齡這個參數傳遞值。如果新增一個只含年級的構造方法,那又將多出許多代碼,更嚴重的是,如果沒有一份詳盡的文檔或者註釋,看到如此多的構造方法將無從下手,這就是非常常見的重疊構造器。
Student student = new Student(“Kevin”, “男”, “0”, “1年級”);
當然還有另外一種方法,只有一個必填項的構造方法,而其他選填項利用setter方法傳遞。例如:
Student student = new Student(“kevin”, “男”); student.setGrade(“1年級”);
這實際上導致了在構造過程中JavaBean可能處於不一致的狀態,也就是說實例化對象本該是一氣呵成,但現在卻分割成了兩大步,這會導致它線程不安全,進一步引發不可預知的後果。
書中提到較為“完美”的解決方案就是利用“Builder模式(建造者模式)”,有關此設計模式可以查看《建造者模式》。這種解決方案屬建造者模式的一種形式,其核心就是不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用構造器(或者靜態工廠),得到一個builder對象,再調用類似setter的方法設置相關可選參數。構建器模式如下所示:
/** * 構建器模式 * Created by yulinfeng on 2017/8/3. */ public class Student { /*必填*/ private String name; private int age; /*選填*/ private String sex; private String grade; public static class Builder { private String name; private int age; private String sex = ""; private String grade = ""; public Builder(String name, int age) { this.name = name; this.age = age; } public Builder sex(String sex) { this.sex = sex; return this; } public Builder grade(String grade) { this.grade = grade; return this; } public Student build() { return new Student(this); } } private Student(Builder builder) { this.name = builder.name; this.age = builder.age; this.sex = builder.sex; this.grade = builder.grade; } }
客戶端代碼:
Student student = new Student.Builder("kevin", 23).grade("1年級").build();
這樣的客戶端代碼很容易邊寫,並且便於閱讀。對於不瞭解的可能來說利用構建器模式編寫Student類不算易事,甚至代碼比重疊構造器的代碼更多。所以當可選參數在很多時,謹慎使用重疊構造器,而是使用構建器模式。
——170803