Effective Java 读书笔记

2,402 阅读4分钟

缘起

最近因为要开发中间件。但是写了好几个版本,都感觉不怎么好。比起一些大厂的实现,感觉在代码架构的设计,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里面算积分,是不是会好很多呢?