一、Java的String对象
java中String对象很特殊,因为它是个不可变对象。每次对String进行截取,拼接都会返回一个新的String对象。正因为这个原因,初学者经常陷入对字符串操作的误区,包括我,呵呵。
两种生成字符串的方式:
String s = new String("strubgette"); //每次执行都会创建一个新的String实例,即使它们在功能方面是等同的。何必呢,呵呵,又不是内存用不完。。。
String s = "strubgtte"; //如果JVM常量池中有该字符串字面常量,则直接将引用赋给s,否则创建一个新的,基本可以保证只用到一个String实例。
二、重用对象之道
1、不可变对象(immutable object)始终应该被重用。
2、不会被修改的可变对象也应该被重用。
反面示例:
public class Person {
private final Date birthDate;
public Person(Date birthDate) {
// 保护性拷贝,Date是可变对象,否则,哈哈,有你受的
this.birthDate = new Date(birthDate.getTime());
}
// DON'T DO THIS!
//判断一个是是否在生育高峰期出生(1946~1964)
public boolean isBabyBoomer() {
// Unnecessary allocation of expensive object
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,这玩意可不是闹着玩的,一个Calendar实例的创建可以买头猪。。。
修正版:
class Person {
private final Date birthDate;
public Person(Date birthDate) {
//保护性拷贝
this.birthDate = new Date(birthDate.getTime());
}
/**
* The starting and ending dates of the baby boom.
*/
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;
}
}
两个Date做成了final静态域,Calendar和TimeZone也只会在Person类初始化时创建一次,不仅代码逻辑上清晰了,而且性能貌似也有了提高。当然,还可以延迟初始化BOOM_START和BOOM_END,但没必要这样做,因为延迟初始化是有风险的。
三、有个名词叫自动装箱(autoboxing)
Java1.5发行版中,一个牛叉的概念出现了,自动装箱。它允许程序员将基本类型和装箱基本类型(Boxed Primitive Type)混用,按需要自动装箱和拆箱。这貌似成了我们Java菜鸟的福音,动不动就来个Integer,它会装箱哈,多牛叉。殊不知,性能刷刷的滑,这回臭大了。。。
来看一个不小心就会犯的错误:
public class Sum {
// Hideously slow program! Can you spot the object creation?
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,打错一个字符,意味着程序需要额外构造大约2^31个多余的Long实例,很蛋疼吧?!
四、最佳编程实践
1、对于同时提供静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。静态的是类级的,跟对象没啥关系,创不创建都能调用里面的方法。
2、不可变对象(immutable object)始终应该被重用。
3、不会被修改的可变对象也应该被重用。
4、要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。