您的位置:首页 > 编程语言 > Java开发

第5条:避免创建不必要的对象

2015-11-10 15:14 579 查看

第5条:避免创建不必要的对象

一般来说,最好能重用对象,而不是在每次需要的时候就创建一个相同功能的新对象。重用方式既快速,又不需要消耗额外的内存。如果对象是不可变的(immutable),它就始终可以被重用。

下面有一个比较极端的例子- -,看看:

String s = new String("stringette");


该语句每次被执行的时候都创建一个新的String实例,但是这些创建对象的动作全部都是多余的。传递给String构造器的参数本身就是一个String类型的实例,在功能方面和new出来的String对象基本无差。应该直接改为
String s = "stringette
;

这个版本只用了一个String实例,而不是每次执行的时候都去创建一个新的,而且,它可以保证对于所有在同一台虚拟机中运行的代码,只要它们包含相同的字符串,这些字符串实例就会被重用。

对于同时提供了静态工厂方法和构造器的不可变类,通常使用静态工厂方法而不是构造器,以避免创建不必要的对象。例如,静态工厂方法
Boolean.valueOf(String)
几乎总是优先于构造器
Boolean(String)
。构造器在每次被调用的时候都会创建一个新的对象,而静态工厂则从来不要求这样做,实际上也不会这么做。

对于上面列举的
Boolean
例子,跟踪
Boolean.java
的代码会发现:

public Boolean(String s) {
this(parseBoolean(s));
}

public static boolean parseBoolean(String s) {
return ((s != null) && s.equalIgnoreCase("true"));
}

public static Boolean valueOf(String s) {
return parseBoolean(s) ? TRUE : FALSE;
}


每调用一次前者就会产生一个boolean类型的变量,要么是true要么是false,但是后者是将
TRUE
FALSE
直接返回给了调用者,这两个变量在
Boolean.java
中早就已经创建好了:

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);


除了重用不可变的对象之外,也可以重用那些已知不会被修改的可变对象。可以看看下面这个例子,其中涉及可变的Date对象,它们的值一点计算出来之后就不再变化。这个类建立了一个模型:其中有一个人,并有一个isBabyBoomer方法,用来检验这个人是否为一个“baby boomer”(生育高峰期出生的小孩),换句话说就是检验这个人是否是1946年到1964年期间出生的。

public class Person {

private final Date birthDate;

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(1964, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >= 0 &&
birthDate.compareTo(boomEnd) < 0;
}
}


依据上面的代码使用,每次
isBabyBoomer
方法被调用的斯之后,都会新建一个Calendar,一个TimeZone和两个Date实例,这是不必要的。下面的版本中使用一个静态的初始化器(initializer),避免了这种效率低下的情况:

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"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1964, 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实例一次,而不是每次调用函数的时候都会去创建。如果isBabyBoom方法被频繁地调用,这种方法将会显著的提高性能,代码的含义也更加的清晰了。将boomStart和boomEnd从局部连良改为final静态域,这些日起显然是被作为常量对待的,从而使得代码更加容易理解。

如果改进之后的
isBabyBoomer
方法永远不会被调用,那就没有必要一开始就初始化
BOOM_START
BOOM_END
域。通过延迟初始化(lazily initializing),即把这些不必要的初始化工作延迟到第一次调用
isBabyBoomer()
的时候再进行。

在之前的例子中,讨论的对象都是可被重用的,因为它们被初始化之后不会再改变了。其他有些情形并不总是这么明显了。考虑适配器(adapter)的情形。适配器是指这样一个对象:它把功能委托给一个后备对象(backup object),从而为后备对象提供一个可代替的接口。由于适配器除了后备对象之外,没有其他的状态信息,所以针对某个给定的特定适配器而言,它不需要创建多个适配器实例。

例如,Map接口的
keySet()
方法返回该Map对象的Set试图,其中包含了该Map所有的键(Key)。粗看起来,好像每次执行
keySet()
方法都因该创建一个新的Set实例,但是,对于一个给定的Map对象,实际上每次调用keySet都返回的是同样一个Set实例。虽然被返回的Set实例一般是可以改变的,但是所有返回的对象引用都指向了同一个实例变量,因此当这个变量里面的数据发生变化的时候,通过所有引用访问到的信息都是一样的。对于这段的解释,还是在
Map.java
中的代码最有说服力:

public Set<K> keySet() {
if (keySet == null) {
keySet = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K,V>> i = entrySet().iterator();

public boolean hasNext() {
return i.hasNext();
}

public K next() {
return i.next().getKey();
}

public void remove() {
i.remove();
}
};
}

public int size() {
return AbstractMap.this.size();
}

public boolean isEmpty() {
return AbstractMap.this.isEmpty();
}

public void clear() {
AbstractMap.this.clear();
}

public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
}
return keySet;
}


上面会检测变量
keySet
变量是否为空,如果为空则遍历所有的Key并加入其中,如果不为空则直接返回该引用。
keySet
在每个Map实例中都有一个实例对象存在。

在Java1.5 发行版本中,有一种创建多余对象的新方法:自动装箱(autoboxing),它允许程序员将基本类型和装箱基本类型混用(类似于Integer和int)。按需求来自动装箱和自动拆箱。看下面的这个例子:

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
,Long属于装箱类型,也就是说在这个计算过程中花费了大量的时间来进行装箱和拆箱,创建了很多无用的Long类型对象。因此要小心的使用装箱类型,优先使用基本类型,避免不必要的装箱过程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息