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; 
    }
}

缺点:

  • 构造过程被分到了多个调用中,一个JavaBean在其构造过程中可能处于不一致的状态。

  • 类无法仅仅通过检查构造器参数的有效性来保证一致性。

(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)总结

  • 第2、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对于类行为需要被底层资源参数化的场景是不适用的。

(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); 
    }
}
posted @ 2019-01-07 09:36  月下小魔王  阅读(253)  评论(0编辑  收藏  举报