一般来说,最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象。重用方式既快速,又流行。如果对象是不可变的,他始终可以被重用。
反面例子:
String S = new String("dali");
该语句每次执行的时候都创建一个新的String实例,但是这些创建对象的动作全部都是不必要的。
改进后:
String s = "dali";
这个版本只用了一个String实例,而不是每次执行的时候都创建一个新的实例。而且,它可以保证,对于所有在同一台虚拟机中运行的代码,只要他们包含相同的字符串字面常量,该对象就会被重用。
除了重用不可变的对象之外,也可以重用那些已知不会被修改的可变对象。
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+8"));
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
Dateboom Start = gmtCal.getTime();
gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
DateboomEnd=gmtCal.getTime();
return birthDate.compareTo(boomStart)>=0&&
birthDate.compareTo(boomEnd)<0;
}
}
isBabyBoomer每次调用的时候,都会创建一个Calendar、一个TimeZone和两个Date实例,这是不必要的。
正确方式:
public class Person{
private final Date birthDate;
public Person(DatebirthDate){
this.birthDate=newDate(birthDate.getTime());
}
private static final Date BOOM_START;
private static final Date BOOM_END;
static{
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
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;
}
}
这种只在初始化时候创建Calendar、TimeZone和Date实例一次,而不是在每次调用isBabyBoomer的时候创建这些实例。如果isBabyBoomer方法被频繁地调用,这种方法将会显著地提高性能。
避免进行隐式装箱
自动装箱是Java5引入的一个特性,即自动将原始类型的数据转换成对应的引用类型,比如int转为Integer等。
这种特性,在编码时稍有不注意就可能创建了不必要的对象了。
反面例子:
Integer sum = 0;
for (int i = 1000; i <5000; i++) {
sum +=i;
}
上面的代码sum+=i可以看成sum =sum+i,但是+不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后在自动装箱转换成Integer对象。其内部实现如下:
int result = sum.intValue() + i;
Integer sum = new Integer(result);
由于这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这个循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我门编程时,需要注意到这一点,正确地声明 变量类型,避免因为自动装箱引起的性能问题。
谨慎选用容器
java提供了很多编辑的容器集合来组织对象。比如ArrayList,HashMap等。
容器虽然使用起来方便,但存在一些问题,就是他们会自动扩容,这其中不是创建新的对象,而是创建一个更大的容器对象。这就意味着将占用更大的内存空间。
以HashMap为例,当我们put key和value时,会检测是否需要扩容,如需要则双倍扩容。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry e = table[bucketIndex];
table[bucketIndex] = new Entry(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
建议:预估一个较大的容量值,避免多次扩容
不要错误地认为本条码所介绍的内容暗示着“创建对象的代价非常昂贵,我们应该要尽可能地避免创建对象”。相反,由于小对象的构造器只做很少量的显式工作,所以,小对象的创建和回收动作是非常廉价的,特别是在现代的JVM实现上更是如此。通过创建附加的对象,提升程序的清晰性、简洁性和功能性,这通常是件好事。