避免创建不必要的对象
当你应该重用现有对象的时候,请不要创建新的对象”。
最为突出的例子莫过于字符串常量的创建,众所周知String字符串有两种创建方式。
String str=“hello";
String str = new String("hello");
第一种String字符串的创建是在方法区(JDK7后改到了堆内存)中的常量池中创建一个”hello”常量,将来若再有一个字符串变量为“hello”时将直接指向常量池中的“hello”变量而不用重新创建;第二种则是会在堆变量中创建一个新的String实例,将来若再以此方式创建一个字符串变量为“hello”时还是会重新创建一个String实例。显然第二种方式创建了一个“不必要”的实例,相比较而言第一种方式更加高效。
另外一个例子则是将”true”变量转换为Boolean类型也有以下两种转换方式:
Boolean boolean = new Boolean("true");
Boolean boolean = Bolean.ValueOf("true");
第一种转换方式也是每次都会在堆内存中创建一个新的Boolean实例;第二种查看其源代码便知不会每次返回一个新的实例,返回的是一个在编译器就已经确定了的static final Boolean型变量:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(String s) {
return toBoolean(s) ? TRUE : FALSE;
}
private static boolean toBoolean(String name) {
return ((name != null) && name.equalsIgnoreCase("true"));
}
书中举了一个例子是否是在1946年至1964年出生来说明,这个例子在现实当中也很常见:
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* “是否在1946-1965年出生”
* 这在现实中应该是比较常见的一种写法
* Created by yulinfeng on 8/5/17.
*/
public class Person {
private final Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
public boolean isBabyBoomer() {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); //新创建Calendar实例
gmtCal.set(1949, Calendar.JANUARY, 1, 0, 0, 0);
Date boomStart = gmtCal.getTime(); //新创建Date实例
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime(); //新创建Date实例
return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
}
}
这样的代码我相信人人都写过类似的,书中提到这种代码的写法每次都创建新的实例对象,而实际上是不必要的,而给出了一种比较高效的代码示例:
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* “是否在1946-1965年出生”
* 这在现实中应该是比较常见的一种写法
* Created by yulinfeng on 8/5/17.
*/
public class Person {
private final Date birthDate;
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); //新创建Calendar实例
gmtCal.set(1949, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime(); //新创建Date实例
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime(); //新创建Date实例
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
}
}
利用静态代码块在类Person初始化的时候创建对象,而在之后调用方法判断时不再每次重新创建新的实例对象,这种写法有点“烧脑”,确实有点“不符合”编码习惯,大概是因为这是需要思考才能写出来的原因吧。
消除过期的引用对象
此条目较为容易理解,之所以要消除过期的对象引用其目的就在于尽量避免内存泄露的问题,书中举了一个“栈”的例子,其中在弹出一个元素时代码如下:
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
可以看到弹出元素时仅仅是将元素弹出后在将数组的索引-1,实际上数组维护的那个元素引用还在,也就是说那个被弹出的元素并不会被GC,如此一来最后就很有可能导致内存泄露的问题。解决此类的办法就是,当不再使用这个元素时,将其置为null。
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
实际上当你写出上面的代码时一定注意这并不是在任意条件下都成立,你应该仔细思考此时的引用是否为过期引用,“清空对象引用应该是一种例外,而不是一种规范行为”。