第5条:避免创建不必要的对象
本条主要讲的是一些反面教材,希望大家引以为鉴。
①无意中使用自动装箱导致多创建对象。
public class Sum {
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
}
sum被声明为Long而不是long,意味着每次i都要被自动装箱(创建Long实例),因此速度会慢很多。
②可以重用不会被修改的可变对象。
也就是说,有些看起来是变量,但是实际上它的值一旦计算出来之后就不再变化,这种也应该视作常量,而不是每次都计算。
下面先看一段代码。
public class Person {
//生日
private final Date birthDate; public Person(Date birthDate) {
this.birthDate = new Date(birthDate.getTime());
} public boolean isBabyBoomer() {
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;
}
}
这段代码中的isBabyBoomer方法,根据生日判断是否属于婴儿潮时间。看起来并没有什么问题。
但实际上,每次调用这个方法时,都会新建一个Calendar,一个TimeZone和两个Date实例,这是没有必要的(所以,在平时写方法时也要注意,有很多对象是可以提取出来的)。下面看如何改进。
public class Person {
private final Date birthDate; public Person(Date birthDate) {
this.birthDate = new Date(birthDate.getTime());
}
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;
}
}
改进之后Person类仅在初始化时创建了实例,后面调用isBabyBoomer方法时,就没有生成对象的消耗了。
第6条:消除过期的对象引用
下面的例子对java中的栈进行一个简单的模拟,看起来逻辑上没什么问题,却隐藏着一个问题。
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();
Object result = elements[--size];
//如果没有这一步,会产生很大的问题
//elements[size] = null;
return result;
} private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
问题就在于,当栈先增长,然后再收缩。从栈中弹出来的对象并不会被当做垃圾回收,这是因为栈内还维护着这些对象的引用。弹出之后,使用栈的程序不再需要它,只能人为清理掉。一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。
文中还还提到在使用回调时,确保回调立即被当作垃圾回收的最佳方法时只保存它们的弱引用,例如,只将它们保存成WeakHashMap中的键(这个平时很少用到,只作为了解)。
弱引用与强引用的区别在于,即使内存不足,强引用也不会被回收掉。而弱引用不管内存足不足,一旦被垃圾回收线程发现,就会被回收掉。