這個靜態工廠,與設計模式中的靜態工廠不一樣,這裏的靜態工廠方法,替換爲「靜態方法」比較好理解,主要就是建議編寫靜態方法來建立對象。java
使用靜態方法的好處:正則表達式
一、靜態方法有名稱,能夠確切地描述功能,好比根據哪些參數,返回哪一種類型;數據庫
二、不須要先建立對象實例,再調用方法來建立所需的對象;使用靜態方法能夠直接使用類名加靜態方法名便可調用;設計模式
三、能夠返回原類型的任何子類型對象;緩存
四、能夠經過傳入不一樣的參數,得到不一樣的返回值;框架
五、方法返回的對象所屬的類,在編寫包含該靜態方法的類時能夠不存在,好比JDBC API;工具
六、使用構造方法建立對象時的開銷,相對於使用靜態方法來講,要大得多,好比Boolean.valueOf(Stirng)與new Boolean(String);性能
缺點和不足ui
一、靜態方法中要想返回對象,那麼該對象所屬的類必須有能夠訪問的構造器,不然不能被實例化;this
二、靜態方法比較難被發現;
當類的構造器或者靜態工廠中具備較多的參數時,或者參數可能後期會增長時,那麼在設計類的時候,可使用Builder模式。
package cn.ganlixin.effective_java; public class BuilderTest { public static void main(String[] args) { final Person person = new Person.Builder().id(1).name("ganlixin").build(); } } class Person { private int id; private String name; private Person(Builder builder) { id = builder.id; name = builder.name; } public static class Builder { // Builder通常來講包含有外層類相同的屬性 private int id; private String name; public Builder id(int val) { this.id = val; return this; } public Builder name(String val) { this.name = val; return this; } public Person build() { return new Person(this); } } }
上面的代碼,使用Lombok的話,只須要使用一個@Builder註解便可:
package cn.ganlixin.effective_java; import lombok.Builder; public class LombokBuilder { public static void main(String[] args) { final Person person = new Person.PersonBuilder().id(1).name("ganlixin").build(); } } @Builder class Person { private int id; private String name; }
單例模式:
一、經過私有化構造器,能夠防止客戶端使用new關鍵字建立對象;
二、提供靜態方法獲取單例對象;
以上兩點存在的問題,可使用AccessibleObject.setAccessible()方法經過反射機制調用構造器,能夠在構造其中判斷是否已經建立實例,若是已建立,則拋出異常;
三、單例對象的序列化問題;
使用枚舉類型建立單例屬性,例子以下:
public class SingletonEnum { public static void main(String[] args) { final OneDemo instance = OneDemo.INSTANCE; instance.test(); } } enum OneDemo { INSTANCE; // 定義其餘function public void test() { // ......... } }
當咱們建立工具類的時候,通常來講工具類不該該被實例化,雖然能夠將工具類聲明爲抽象類來避免被實例化,可是這樣並很差,由於抽象類的子類能夠實例化!
解決方案:顯示聲明工具類的無參構造方法,在其中拋出異常便可。
class MyUtil { private MyUtil(){ throw new RuntimeException("工具類不該該被實例化"); } // 聲明工具類中的方法 public static void myMethod(){} }
主要就是講依賴注入。
問題是這樣的:有一個郵件白名單,對於接收到的郵件,須要先進行過濾一遍(匹配白名單),那麼可能會寫出下面的代碼:
// 郵件過濾器 class MailChecker{ // 白名單列表,在代碼中寫死 private List<String> whiteList = new ArrayList<String>(){{ add("123"); add("456"); add("789"); }}; // 進行檢驗操做 public boolean isValid(String email) { return whiteList.contains(email); } }
上面的代碼其實並很差,緣由:
一、過濾名單寫死了;
二、只能進行郵件過濾;這一點可能會有疑惑,功能專注很差嗎?這沒問題,可是若是有一個「電話過濾器」是否是又要有一個PhoneChecker呢?
推薦作法:在過濾器中不寫死whiteList,白名單由外部傳入,過濾器的功能就是進行過濾而已,改成以下:
public class UseInject { public static void main(String[] args) { // 假設phoneList是外界傳入的,或者是文件中讀入的 List<String> phoneList = null; Checker phoneChecker = new Checker(phoneList); phoneChecker.isValid("123"); } } class Checker{ private List<String> whiteList; // 接收外界傳入的白名單(能夠是電話、郵箱、地址....) public Checker(List<String> whiteList) { this.whiteList = whiteList; } // 檢驗操做 public boolean isValid(String val) { return whiteList.contains(val); } }
若是是使用Spring這些框架,能夠直接利用依賴注入,好比:
public class UseInject { @Value("${phoneList}") private static List<String> phoneList; public static void main(String[] args) { Checker phoneChecker = new Checker(phoneList); phoneChecker.isValid("123"); } }
一、雖然Java有自動裝箱和自動裝箱,可是請儘可能使用基本數據類型;
二、對於提供了靜態工廠方法和構造器的類來講,應該優先使用靜態方法而非構造器建立對象;
三、有一些對象不該該被重複建立,好比正則表達式的Pattern實例;
四、應該避免維護本身的對象池,數據庫鏈接池除外,由於創建數據庫鏈接的代價比較大,建立普通對象的代價則相對來講很小;
五、不要太死板,有些時候,爲了不建立沒必要要的對象,會付出更多的代價;
一、若是類是本身管理內存,那麼就須要須要警戒內存泄漏問題,一旦元素被釋放掉,則該元素包含的任何對象應用都應該被清空;
二、緩存致使內存泄漏,可使用WeakHashMap;
三、若是提供服務給客戶端調用,要警戒客戶端的反覆調用是否會反覆產生對象,可是卻沒有清理垃圾;
一、終結方法(finalizer)和清除方法(cleaner)不能保證會被及時的執行,從一個對象變爲不可達開始,到執行這兩個方法的事件是任意長的,因此,注重時間的任務不該該由finalizer和cleaner來完成;
二、永遠不該該依賴finalizer和cleaner方法來更新重要的持久狀態,由於這兩個方法可能沒有機會被執行;
三、System.gc和System.runFuinalization只能增長finalizer和cleaner的執行機會,注意,增長可能性,但不是保證,100%;
四、finalizer中出現異常,終結過程會終止,可怕吧,想死還死不掉;
五、下降程序性能、finalizer attack;
要使用try-with-resource,須要類實現AutoCloseable接口(大多數類都實現了)。
下面以書中的例子舉例:
public static void notSuggest() throws IOException { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new FileReader("demo.txt")); bufferedReader.readLine(); } finally { bufferedReader.close(); } // 注意,上面的demo.txt不存在時,報異常FileNotFoundException,而後finally中的close也會報錯,可是close的異常會覆蓋readLine的異常 // 因此調用notSuggest()方法,只會看到close的異常Exception in thread "main" java.lang.NullPointerException }
改爲下面這樣就能夠捕獲全部異常了,可是看起來,代碼好臃腫:
public static void notSuggest2() { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new FileReader("demo.txt")); bufferedReader.readLine(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } // 雖然能夠打印出全部的異常信息(FileNotFoundException和close的NullPointerException異常) // 按照常識,若是前面一步已經錯了,就不該該再走下去,可是FileNotFoundException以後,仍是會執行finally中的close,結果又出現異常 }
推薦使用try-with-resource
public static void suggest() { try (BufferedReader bufferedReader = new BufferedReader(new FileReader("demo.txt"))) { bufferedReader.readLine(); } catch (Exception e) { e.printStackTrace(); } // 不須要catch每個子異常,好比FileNotFoundException,直接寫一個IOException或者Exception便可 // 上面demo.txt不存在,因此會出現java.io.FileNotFoundException,可是沒有報第二個異常哦 }