《Effective Java 2nd》第2章 创建和销毁对象
第2章 创建和销毁对象
第1条:考虑使用静态工厂方法代替构造器
通过使用静态工厂方法而不是使用构造器来创建类。
举例:Boolean.valueOf(boolean)方法,将boolean转换为Boolean对象引用。
有以下优势:
1)静态工厂方法有名称,可以表示方法的意思
2)不必在每次调用的时候都创建新对象(对象缓存能力)。
不可变类可预先构建实例,缓存起来重复使用。
3)可以返回原返回类型的任何子类型的对象
4)在创建参数化类型的时候代码更简洁。
//我们平时创建list List<String> list = new Arraylist<>(); //使用google 工具类 List<String> list = Lists.newArrayList();
valueOf、of、getInstance、newInstance、getType、newType(如newArrayList)
第2条:遇到多个构造器参数时考虑用构建器
当有很多个构造参数,且有几个参数是可选的,考虑使用Builder
public class NutritionFacts { /** 必填参数 */ private final int servingSize; private final int servings; /** 可选参数 */ private final int fat; private final int sodium; public static class Builder { private final int servingSize; private final int servings; private int fat = 0; private int sodium = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder fat(int fat) { this.fat = fat; return this; } public Builder sodium(int sodium) { this.sodium = sodium; return this; } public P build() { return new P(this); } } private NutritionFacts(Builder builder) { this.servingSize = builder.servingSize; this.servings = builder.servings; this.fat = builder.fat; this.modium = builder.modium; } }
客户端代码
NutritionFacts p = new NutritionFacts.Builder(200, 8).fat(100).sodium(35).build();
更高级使用:
public interface Builder<T> { public T build(); }
public static class NutritionFacts.Builder implements Builder<P>
这样可以将Builder<NutritionFacts>传给方法,并结合抽象工厂创建NutritionFacts实例。
第3条:用私有构造器或者枚举类型强化Singleton属性
举例1:使用私有构造器
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} //私有构造器 //other code }
为了保证Singleton类是可序列化的
1)声明加上implents Serializable
2) 所有实例域都是transient
3)提供一个readResolve方法
private Object readResolve() { return INSTANCE; }
举例2:使用枚举
public enum Singleton { INSTANCE; }
第4条:通过私有构造器强化不可实例化的能力
主要用于在写工具类的时候。
public class XXXUtils { private XXXXUtils() { } //other code }
第5条:避免创建不必要的对象
当你应该重用现有对象的时候,不要创建新的对象。
举例1:
m方法会被频繁调用时,会创建n多的String实例。
public String m() { String s = new String("str"); //other code }
改进后,m方法被频繁调用,但是s会被复用。
public String m() { String s = "str"; }
举例2:
求所有Integer的和,因声明为Long sum,而不是long sum,程序将创建约2的31次方个Long实例。
public static void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; //这里每次都会创建一个Long实例。要当心无意识的自动装箱。 } System.out.println(sum); }
第6条:消除过期的对象引用
主要讲了内存泄露问题。
举例1:类自己管理内存,易导致内存泄露。
下面是简单的栈实现,程序每次测试都会通过,但是有个隐藏问题——”内存泄露“。
栈先增长,再收缩,栈中弹出的对象不会被当做垃圾回收,即使使用栈的程序不再引用这些对象。
public class Stack { private Object[] elements; private int size; private static final int DEFAULT_INIT_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INIT_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) { throw new EmptyStackException(); } Object result = elements[--size]; //解决内存泄露的方法 //elements[size] = null; 清空过期引用 return result; } }
为何会出现内部泄露?
Stack通过数组保存数组元素,相当于自己管理内存。只要是类自己管理内存,就应该警惕内存泄露问题。
举例2:内存泄露的另一个常见来源是缓存
缓存内存泄露:把对象引用放到缓存中,容易被遗忘,不再有用之后仍留在缓存中。
情形1:只要在缓存外有对某个项的键的引用,该项就有意义。
解决方法:使用WeakHashMap。
(记住只有当缓存项生命周期由该键的外部引用而不是值决定时,WeakHashMap才有意义)
情形2:缓存项生命周期是否有意义,不是很容易确定
解决方法:后台线程定期清理 or 缓存添加新条目时顺便清理(LinkedHashMap.removeEldestEntry()方法)
情形3:更复杂的缓存
解决方法:使用java.lang.ref
举例3:内存泄露的第三个常见来源是监听器和其他回调
比如注册回调,但是没有显式地取消回调。解决方法:保存它们的弱引用(weak ref),如只将它们保存为WeakHashMap的键。
第7条:避免使用终结方法
终结方法就是finalize()方法。
Java语言规范不保证终结方法会被及时地执行,更不保证一定会被执行。
System.gc()增加了终结方法被执行的机会,但不保证一定会被执行。