Effective Java 讀書筆記(一):建立和銷燬對象

1 構造器 => 靜態工廠方法

(1)優點

  • 靜態工廠方法有名字
  • 靜態工廠方法沒必要在每次被調用時都產生一個新的對象
  • 靜態工廠方法能返回原返回類型的任意子類型的對象
  • 靜態工廠方法根據調用時傳入的不一樣參數而返回不一樣類的對象
  • 靜態工廠方法返回對象的類不須要存在(SPI架構)

(2)限制

  • 沒有公有或者保護構造方法的類不能子類化(可是能夠鼓勵咱們使用組合模式,而不是繼承模式)
  • 靜態工廠方法難以發現

(3)經常使用靜態工廠方法命名

  • from:傳入單個參數,返回該類型實例
Date d = Date.from(instant);
  • of:傳入多個參數,返回一個包含這些參數的該類型實例
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf:from和of的替換方案
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance or getInstance:建立一個由參數(若是有的話)描述的實例
StackWalker luke = StackWalker.getInstance(options);
  • create or newInstance:相似instance或getInstance, 但保證每次調用都返回新實例
Object newArray = Array.newInstance(classObject, arrayLen);
  • getType:相似getInstance,但通常在工廠方法包含在不一樣類的狀況下使用。Type是工廠方法返回的對象的類型。
FileStore fs = Files.getFileStore(path);
  • newType:相似於newInstance,但通常在工廠方法包含在不一樣類的狀況下使用。
BufferedReader br = Files.newBufferedReader(path);
  • type:getType和newType簡潔的替換方式
List<Complaint> litany = Collections.list(legacyLitany);

2 構造器 => 構建者

(1)問題

public class NutritionFacts {
    private final int servingSize; // (mL) required 
    private final int servings;    // (per container) required
    private final int calories;    // (per serving) optional    
    private final int fat;         // (g/serving) optional
    private final int sodium;      // (mg/serving) optional
    private final int carbohydrate; // (g/serving) optional
    public NutritionFacts(int servingSize, int servings) { 
        this(servingSize, servings, 0);
    }
    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0); 
    }
    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0); 
    }
    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0); 
    }
    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, 
        int carbohydrate) {
        this.servingSize = servingSize; this.servings = servings;
        this.calories = calories
        this.fat = fat
        this.sodium = sodium
        this.carbohydrate = carbohydrate;
    } 
}
  • 構造器方法數量迅速膨脹,由於有大量的可選項
  • 可伸縮構造器(使用可變參數)是可行,只是當有不少參數時,會讓客戶端代碼很難編寫,並且代碼也很難閱讀。

(2)替代方案:JavaBeans模式

public class NutritionFacts {
    private int servingSize = -1; // Required; no default value 
    private int servings = -1; // Required; no default value
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;
    public NutritionFacts() {}
    // Setters
    public void setServingSize(int val) { 
        servingSize = val; 
    } 
    public void setServings(int val) { 
        servings = val; 
    }
    public void setCalories(int val) {
        calories = val;
    }
    public void setFat(int val) {
        fat = val;
    }
    public void setSodium(int val) {
        sodium = val;
    }
    public void setCarbohydrate(int val) { 
        carbohydrate = val; 
    }
}

缺點:html

  • 構造過程被分到了多個調用中,一個JavaBean在其構造過程當中可能處於不一致的狀態。java

  • 類沒法僅僅經過檢查構造器參數的有效性來保證一致性。數據庫

(3)替代方案:Builder模式

  • 例1
// Builder Pattern
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;
        // Optional parameters - initialized to default values
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        } 
        public Builder calories(int val){ 
            calories = val; return this; 
        }
        public Builder fat(int val){ 
            fat = val; return this; 
        }
        public Builder sodium(int val){ 
            sodium = val; return this; 
        }
        public Builder carbohydrate(int val){ 
            carbohydrate = val; return this; 
        }
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    } 
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

// 使用
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
                                            .calories(100)
                                            .sodium(35)
                                            .carbohydrate(27)
                                            .build();
  • 例2
public abstract class Pizza {
    public enum Topping { 
        HAM, MUSHROOM, ONION, PEPPER,SAUSAGE 
    }
    final Set<Topping> toppings;
    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings =
        EnumSet.noneOf(Topping.class);
        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        } 
        abstract Pizza build();
        protected abstract T self();
    } 
    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone(); // See Item 50
    }
}

public class NyPizza extends Pizza {
    public enum Size { 
        SMALL, MEDIUM, LARGE 
    }
    private final Size size;

    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;
        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        } 
        public NyPizza build() {
            return new NyPizza(this);
        } 
        protected Builder self() { 
            return this; 
        }
    } 

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
} 

NyPizza pizza = new NyPizza.Builder(SMALL)
                        .addTopping(SAUSAGE)
                        .addTopping(ONION).build();

優點:數組

  • builder能擁有多個可變參數(例1)
  • builder能將傳入到不一樣方法裏的參數聚合起來而後傳入單個域裏(例2)

缺點:緩存

  • 多建立一個Builder對象的內存開銷

3 使用枚舉強化單例模式

(1)餓漢

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
    public void leaveTheBuilding() {} 
}

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
    public void leaveTheBuilding() {} 
    
    public static Elvis instance(){
        return INSTANCE;
    }
}

(2)內部類

public class Elvis {
    private Elvis() {}
    public void leaveTheBuilding() {} 
    
    // 經過類加載機制來保證線程安全
    private static class Holder{
        private static final Elvis INSTANCE = new Elvis();
    }
    
    public static Elvis instance(){
        return Holder.INSTANCE;
    }
}

(3)雙重檢驗鎖

public class Elvis {
    private Elvis() {}
    public void leaveTheBuilding() {} 
    
    private static volatile Elvis INSTANCE;
    
    public static Elvis instance(){
        if(INSTANCE == null){
            synchronized(Elvis.class){
                if (INSTANCE == null) {  
                    INSTANCE = new Elvis();  
                } 
            }
        }
        return INSTANCE;
    }
}

(4)枚舉

public enum Elvis {
    INSTANCE;
    public void leaveTheBuilding() {} 
}

(5)總結

  • 第二、3種具有懶加載
  • 第4種具有禁止反序列化建立對象問題安全

  • 第1~3種若是實現了Serializable接口的補償措施架構

    // 方法1
    private static Class getClass(String classname) throws ClassNotFoundException {
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      if(classLoader == null)     
             classLoader = Singleton.class.getClassLoader();     
    
          return (classLoader.loadClass(classname));     
       }     
    }  
    
    // 方法2
    // 重寫readReslove方法,須要將全部成員變量聲明爲transient
    private Object readResolve() {     
        return INSTANCE;     
    }

參考:併發

4 私有化構造器強化不可實例化的能力

public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
     // Remainder omitted 
}

5 優先使用依賴注入而不是硬鏈接資源

靜態工具類和Singleton對於類行爲須要被底層資源參數化的場景是不適用的。ide

(1)構造器注入

public class SpellChecker {
    private final Lexicon dictionary; // 所需的底層資源
    
    public SpellChecker(Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    } 
    
    public boolean isValid(String word) { ... }
    public List<String> suggestions(String typo) { ... }
}

(2)Setter注入

public class SpellChecker{
    private Lexicon dictionary; // 所需的底層資源
    
    void setDictionary(Lexicon dictionary){
        this.dictionary = dictionary;
    }
    
    public boolean isValid(String word) { ... }
    public List<String> suggestions(String typo) { ... }
}

(3)接口注入

public interface DictionaryDependent{
    void setDependence(Lexicon dictionary);
}

public class SpellChecker implement DictionaryDependent{
    private Lexicon dictionary; // 所需的底層資源
    
    void setDependence(Lexicon dictionary){
        this.dictionary = dictionary;
    }
    
    public boolean isValid(String word) { ... }
    public List<String> suggestions(String typo) { ... }
}

參考:工具

6 避免建立沒必要要的對象

  • String s = "bikini"; ====> String s = new String("bikini");
  • Boolean.valueOf(String) :內部只維護了兩個對象TRUEFALSE
  • 判斷是否爲數字
// 每次都生成Pattern對象,形成內存浪費
static boolean isRomanNumeral(String s) {
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 
}

public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
                + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    static boolean isRomanNumeral(String s) { 
        return ROMAN.matcher(s).matches();
    } 
}
  • Map.keySet():每次返回同一個實例,懶加載模式
  • 注意自動裝箱問題
  • 儘可能不要維護對象池,除非對象建立開銷太大,如:JDBC數據庫鏈接池

7 消除過期的對象引用

(1)內存泄漏存在狀況與解決

  • 一個類本身管理它的內存(沒用的對象沒有置空):顯式置null
  • 緩存相關:
    • 設置淘汰策略:設置超時清除
    • 自制回收線程
    • 內存佔用作限制
    • 使用WeakHashMap容器
  • 監聽器相關:
    • 使用WeakHashMap容器

(2)WeakHashMap的實現原理

  • Entry繼承WeakReference類:下一次垃圾回收就會回收掉該對象
  • WeakHashMap內部一個ReferenceQueue:被回收的對象將放入該隊列中
  • 任何對WeakHashMap的操做都會進行一次同步操做:ReferenceQueue與Entry[]的同步操做,把Entry過時對象清除,ReferenceQueue清空。
// 回收操做和把對象放入引用隊列由JVM處理,WeakHashMap只須要建立WeakReference時,把ReferenceQueue放入便可

// 同步操做:該方法被WeakHashMap中的全部操做涉及,表明只要進行操做就會進行同步,可能你會擔憂性能問題,可是實際上若是queue中沒有數據時,直接就返回了。
private void expungeStaleEntries() {
    // 循環清除queue中的元素
    for (Object x; (x = queue.poll()) != null; ) {
        // 防止併發
        synchronized (queue) {
            @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);

            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    // 協助GC操做,清除數組中的元素
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

8 避免使用finalize方法

  • finalize的調用時機沒法把握
    • finalizer線程優先級低,可能還沒回收就發生OOM異常
    • System.gc也沒法保證必定會執行
  • 致使嚴重的性能損失
  • 類暴露於終結方法攻擊:
    • 在終結過程當中如有未被捕獲的異常拋出,則拋出的異常會被忽略,並且該對象的終結過程也會終止。
    • 當構造器或者序列化中拋出異常,惡意子類的終結方法能夠運行在本應夭折的只構造了部分的對象上(強行救活父類對象)。此時子類就能夠調用該對象上的任意方法,但實際上該對象應該不存在纔對。
    • final類能免疫於此類攻擊,由於沒有類能對final類進行惡意繼承。
    • 爲了防止非final類遭受終結方法攻擊,咱們能夠寫一個什麼都不作並且是final的終結方法。
  • 替代方案:繼承AutoCloseable接口,close方法在被關閉後還被調用,就要拋出一個IllegalStateException異常。
public class Room implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
    
    // Resource that requires cleaning. Must not refer to Room!
    private static class State implements Runnable {
        int numJunkPiles; 
        // Number of junk piles in this room
        State(int numJunkPiles) {
            this.numJunkPiles = numJunkPiles;
        }
        
        // Invoked by close method or cleaner
        @Override 
        public void run() {
            System.out.println("Cleaning room");
            numJunkPiles = 0;
        }
    } 
    // The state of this room, shared with our cleanable
    private final State state;
    // Our cleanable. Cleans the room when it’s eligible for gc
    private final Cleaner.Cleanable cleanable;
    public Room(int numJunkPiles) {
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    } 
    @Override 
    public void close() {
        cleanable.clean();
    }
}

9 優先使用try-with-resources而不是try-finally

  • try-finally
static void copy(String src, String dst) throws IOException {
    InputStream in = new FileInputStream(src); 
    try {
        OutputStream out = new FileOutputStream(dst); 
        try {
            byte[] buf = new byte[BUFFER_SIZE]; 
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n); 
        } finally {
            out.close();
        }
    } finally {
        in.close(); 
    }
}
  • try-with-resources
static void copy(String src, String dst) throws IOException {
    try (
        InputStream in = new FileInputStream(src); 
        OutputStream out = new FileOutputStream(dst)
    ) {
        byte[] buf = new byte[BUFFER_SIZE]; int n;
        while ((n = in.read(buf)) >= 0)
            out.write(buf, 0, n); 
    }
}
相關文章
相關標籤/搜索