Effective Java 讀書筆記

徐家三少發表於2016-11-23

緣起

最近因為要開發中介軟體。但是寫了好幾個版本,都感覺不怎麼好。比起一些大廠的實現,感覺在程式碼架構的設計,java語言的基礎知識上面還是掌握的不夠牢靠。於是打算重讀一些比較經典的書籍。《Effective Java》就是其中一本。

第一章

主要講解了物件的建立和銷燬。多用工廠和建造者模式,儘量避免冗長的構造方法。其中對於單例的建立,提到了有關在單例序列化/反序列化的時候兩個個陷阱:

  1. 在通過私有構造器,例項化公有final靜態域的反序列化的時候如果是僅僅是繼承Serializable介面不是不夠的。
public class User implements Serializable {

    private static final long serialVersionUID = -672817526808710807L;

    private static final User user = new User();

    private String age;

    private String name;

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private User() {
        this.age = "3";
        this.name = "java";
    }

    public static User getInstance() {

        return user;
    }

    private Object readResolve() {
        return  user;
    }
}

public class SerializableDemo {


    @SuppressWarnings("resource")
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/tiny/Desktop/test.txt"));
        out.writeObject(User.getInstance());
        FileInputStream fis = new FileInputStream(new File("/Users/xujianxing/Desktop/test.txt"));  
           ObjectInputStream ois = new ObjectInputStream(fis);  
           User u = (User) ois.readObject();  

           System.out.println(u.getName());

           if(User.getInstance()==u){
               System.out.println("equals");  
           }
           else{
               System.out.println("not equals");  
           }
    }

}複製程式碼

輸出not equals。
將User物件,稍稍改造:

package demo;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = -672817526808710807L;

    private static final User user = new User();

    private String age;

    private String name;

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private User() {
        this.age = "3";
        this.name = "java";
    }

    public static User getInstance() {

        return user;
    }

    private Object readResolve() {
        return  user;
    }
}
package demo;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = -672817526808710807L;

    private static final User user = new User();

    private String age;

    private String name;

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private User() {
        this.age = "3";
        this.name = "java";
    }

    public static User getInstance() {

        return user;
    }

    private Object readResolve() {
        return  user;
    }
}複製程式碼

增加一個readResolve方法。

  1. 使用反射。通過AccessibleObject.setAccessible強制呼叫私有構造器。就像這樣:
public class SerializableDemo {
    @SuppressWarnings("resource")
    public static void main(String[] args)
            throws InvocationTargetException, InstantiationException, IllegalAccessException, IllegalArgumentException {

        Class<?> clazz = User.getInstance().getClass();

        Constructor[] constructors = clazz.getDeclaredConstructors();

        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName());
            constructor.setAccessible(true); // AccessibleObject
            System.out.println(constructor.newInstance(null));
            System.out.println(User.getInstance());
        }
    }

}複製程式碼

如果要抵禦這種攻擊。可以修改構造器,但在構造器中丟擲異常不是一個好的實踐。並且會破壞final域。

關於建造者模式

在某些情況下,可以利用建造者模式來大幅優化業務程式碼,使得業務程式碼看起來也十分優雅,並且具有很強的擴充套件性。比如有一個攝影師系統。攝影師的積分規則如下:

  1. 每個訂單+10分;
  2. 使用者加片:每加一張+1分;
  3. 使用者評價:五星+5分,四星+1分,三星+0分,二星-5分,一星-1;
  4. 上傳獎懲積分:拍攝日當天上傳,每單+10分。
  5. 第二天上傳,每單+5分。第三天上傳,每單+0分。第四天上傳,每單-5分。以此類推,每延後一天,每單-5分。第20天之後上傳,永久取消認證攝影師資格。
  6. 攝影師競標,未被使用者選中,補償2分。
  7. 攝影師作品管理上傳一套加1分。

如果直接在業務系統裡面計算程式碼,一但業務改變,那麼業務中的程式碼也會改變,這樣就違背了“對擴充套件開放,對修改關閉"的原則。使用建造者模式可以減少對業務系統的入侵。



public class PointRule {

    //積分變動記錄
    private    Multimap<String, String> myMultimap =null; 

    private int changeTotal;

    public int getChangeTotal() {
        return changeTotal;
    }

    public void setChangeTotal(int changeTotal) {
        this.changeTotal = changeTotal;
    }

    private PointRule(int changeTotal,Multimap<String, String>   record) {

        this.changeTotal = changeTotal;
        this.record=record;
    }


    private static final int PLUS_NEW_ORDER = 10;
    private static final int PLUS_ADD_SHEET = 1;
    private static final int PLUS_COMMENT_FIVE = 5;
    private static final int PLUS_COMMENT_FOUR = 10;
    private static final int PLUS_UPLOAD_CURRENT_DAY = 10;
    private static final int PLUS_UPLOAD_SECOND_DAY = 5;
    private static final int PLUS_BID_FAIL = 2;
    private static final int PLUS_UPLOAD_WORK = 1;
    private static final int REDUCE_UPLOAD_DELAY = -5;

    public static class Builder {
        private int total = 0;
        Multimap<String, String> myMultimap = ArrayListMultimap.create(); 
        public static Builder getInstance() {
            return new Builder();
        }

        public Builder plusNewOrder(String orderId) {
            total += PLUS_NEW_ORDER;
            this.record.put("orderId", "plusNewOrder");
            return this;
        }

        public Builder plusAddSheet(String orderId) {
            total += PLUS_ADD_SHEET;
            this.record.put("orderId", "plusAddSheet");
            return this;
        }

        public Builder plusCommentFive(String orderId) {
            total += PLUS_COMMENT_FIVE;
            this.record.put("orderId", "plusCommentFive");
            return this;
        }

        public Builder plusCommentFour() {
            total += PLUS_COMMENT_FOUR;
            this.record.put("orderId", "plusCommentFour");
            return this;
        }

        public Builder plusUploadCurrentDay() {
            total += PLUS_UPLOAD_CURRENT_DAY;
            this.record.put("orderId", "plusUploadCurrentDay");
            return this;
        }

        public Builder plusUploadSecondDay() {
            total += PLUS_UPLOAD_SECOND_DAY;
            this.record.put("orderId", "plusUploadSecondDay");
            return this;
        }

        public Builder plusBidFail(String orderId) {
            total += PLUS_BID_FAIL;
            this.record.put("orderId", "plusBidFail");
            return this;
        }

        public Builder plusUploadWork(String orderId) {
            total += PLUS_UPLOAD_WORK;
            this.record.put("orderId", "plusUploadWork");
            return this;
        }

        public Builder reduceUploadDelay(String orderId) {
            total += REDUCE_UPLOAD_DELAY;
            this.record.put("orderId", "reduceUploadDelay");
            return this;
        }

        public PointRule build() {
            PointRule pointRule = new PointRule(this.total,this.record);
            return pointRule;

        }

    }



}複製程式碼

假設有一個Service:

public class PhotographerService {


public  void   userConfirmSheetOrder(){


//使用者收片的邏輯。
PointRule.Builder  buidler=PointRule.Builde.getInstance();
if(當天收片){


buidler.plusCurrentDay();
}

if(修了10張片){

(for int  i=0; i<10;i++){

buidler.plusAddSheet();
}


//其他的業務邏輯


最後得到這次要改變的分數:
PointRule  rule=buidler.builder();


//如果要將積分變動的記錄入庫。那麼可以迴圈PointRule 的map物件。
}複製程式碼

對於積分變動記錄,可以使用迭代器模式。當讓這個例子只是一個舉例說明。比起直接在service裡面算積分,是不是會好很多呢?

相關文章