第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类型对象。因此要小心的使用装箱类型,优先使用基本类型,避免不必要的装箱过程。
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树
- [原创]java局域网聊天系统