【01】Effective Java - 對象建立銷燬

一、術語

(1)Java語言支持四種類型:接口、類、數組、基本類型(primitive),前三種爲引用類型,而基本類型的值不是對象。java

(2)方法的簽名包括它的名稱和全部參數的類型,簽名不包括它的返回類型。sql


二、靜態工廠替代構造器

(1)靜態工廠有名稱數據庫

(2)沒必要再每次調用都建立一個新的對象數組

public static Boolean valueOf(boolean b){
   return b? Boolean.TRUE : Boolean.FALSE;
}

(3)能夠返回原返回類型的任何子類對象緩存

    適用於返回類型爲接口/抽象類安全

public interface Service {
    // Service-specific methods go here
}

public interface Provider {
    Service newService();
}

public class Services {
    private Services() { }  // Prevents instantiation (Item 4)

    // Maps service names to services
    private static final Map<String, Provider> providers =
        new ConcurrentHashMap<String, Provider>();
    public static final String DEFAULT_PROVIDER_NAME = "<def>";

    // Provider registration API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME, p);
    }
    public static void registerProvider(String name, Provider p){
        providers.put(name, p);
    }

    // Service access API
    public static Service newInstance() {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service newInstance(String name) {
        Provider p = providers.get(name);
        if (p == null)
            throw new IllegalArgumentException(
                "No provider registered with name: " + name);
        return p.newService();
    }
}

    客戶端
ide

public class Test {
    public static void main(String[] args) {
        // Providers would execute these lines
        Services.registerDefaultProvider(DEFAULT_PROVIDER);
        Services.registerProvider("comp",  COMP_PROVIDER);
        Services.registerProvider("armed", ARMED_PROVIDER);

        // Clients would execute these lines
        Service s1 = Services.newInstance();
        Service s2 = Services.newInstance("comp");
        Service s3 = Services.newInstance("armed");
        System.out.printf("%s, %s, %s%n", s1, s2, s3);
    }

    private static Provider DEFAULT_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override public String toString() {
                    return "Default service";
                }
            };
        }
    };

    private static Provider COMP_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override public String toString() {
                    return "Complementary service";
                }
            };
        }
    };

    private static Provider ARMED_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override public String toString() {
                    return "Armed service";
                }
            };
        }
    };
}

(4)建立參數化實例更簡潔性能

public static <K,V> HashMap<K,V> newInstance(){
   return new HashMap<K,V)();
}

(5)靜態工廠慣用名稱ui

valueOf,of,getInstance,newInstance,getType,newType


三、用Builder模式替代多參數的構造器

    構造器的參數太多的話,調用方容易混淆,特別是針對相鄰的同類型的參數,若是不當心將順序顛倒,則編譯時難以發現,運行時出問題。this

(1) 重疊構造器版本,好處是安全性,壞處是難以閱讀

public class NutritionFacts {
    private final int servingSize;   // (mL)            required
    private final int servings;      // (per container) required
    private final int calories;      //                 optional
    private final int fat;           // (g)             optional
    private final int sodium;        // (mg)            optional
    private final int carbohydrate;  // (g)             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;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola =
            new NutritionFacts(240, 8, 100, 0, 35, 27);
    }
}

(2)JavaBean版本,好處是可讀性,壞處是本身要保證線程安全

public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize  = -1;  // Required; no default value
    private int servings     = -1;  //     "     "     "      "
    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; }


    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts();
        cocaCola.setServingSize(240);
        cocaCola.setServings(8);
        cocaCola.setCalories(100);
        cocaCola.setSodium(35);
        cocaCola.setCarbohydrate(27);
    }
}

(3)Builder版本,兼容兩者好處

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 carbohydrate  = 0;
        private int sodium        = 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 carbohydrate(int val)
            { carbohydrate = val;  return this; }
        public Builder sodium(int val)
            { sodium = 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;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
            calories(100).sodium(35).carbohydrate(27).build();
    }
}

  newInstance方法可充當build方法的一部分,可是它老是企圖去調用類的無參構造器,若是類沒有無參構造器,編譯時是不會暴露出來的,只能到客戶端調用運行時才暴露。

(4)實踐版

   通常好比sql構造之類的,參數太多,能夠用builder模式,普通bean的賦值,採用builder反而搞得太過負責,工業上的最佳實踐,通常是採用JavaBean + Design by Contract 的模式,要求開發者根據約定傳參數,service層再進行一層校驗,來確保必填參數是不爲null的。


四、私有構造器或枚舉強化Singleton屬性

    (1)私有構造器可以防止經過反射調用去構造實例

    (2)爲確保反序列化以後仍是單例,須要重寫readResolve方法,return INSTANCE,或者直接使用枚舉單例,它內置了反序列化單例的功能。


五、避免建立沒必要要的對象

   (1)達到儘可能重用對象的目的,好比字符串字面常量,不可變的對象。

反例

public class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods omitted

    // DON'T DO THIS!
    public boolean isBabyBoomer() {
        // Unnecessary allocation of expensive object
        Calendar gmtCal =
            Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomEnd = gmtCal.getTime();
        return birthDate.compareTo(boomStart) >= 0 &&
               birthDate.compareTo(boomEnd)   <  0;
    }
}

正解:使用靜態初始化器,初始化起止日期

class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods

    /**
     * The starting and ending dates of the baby boom.
     */
    private static final Date BOOM_START;
    private static final Date BOOM_END;

    static {
        Calendar gmtCal =
            Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_START = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_END = gmtCal.getTime();
    }

    public boolean isBabyBoomer() {
        return birthDate.compareTo(BOOM_START) >= 0 &&
               birthDate.compareTo(BOOM_END)   <  0;
    }
}

(2)優先使用基本類型,而不是封裝類型,小心無心識的自動裝箱額外產生的對象,下面的sum聲明爲long,便可減小2的31次方個多餘的Long實例。

public class Sum {
    // Hideously slow program! Can you spot the object creation?
    public static void main(String[] args) {
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
}

(3)對象建立的誤解

   誤解:對象建立的代價很是昂貴,應該避免建立對象。

   正解:小對象的建立,構造器只作不多量的顯式工做,其建立和回收都是很是廉價的,於是對小對象不必搞什麼對象池之類的;只有重量級的對象,好比數據庫鏈接池等,鏈接數據庫代價是昂貴的,採用對象池正好。


六、消除過時引用

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * Ensure space for at least one more element, roughly
     * doubling the capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

  出棧的時候沒有清空引用

public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];  
        elements[size] = null;   
        return result;
    }

(1)清空對象引用應該是一種例外,而不是一種行爲規範,沒必要每次使用變量的時候,都緊張兮兮的,考慮要不要在使用後賦值爲null

(2)內存泄露的常見來源

A、本身管理內存的容器

B、緩存

C、監聽器和回調方法(註冊監聽,不須要的時候沒有取消監聽)


七、避免使用finalize方法

(1)JVM不保證finalize方法會被及時執行,並且根本不保證它們會被執行

(2)不要依賴finalize方法去關閉重要資源,好比關閉文件、關閉數據庫鏈接,通常採用finally語句便可

(3)使用finalize可能會形成額外的性能損失

相關文章
相關標籤/搜索