Effective Java学习之——避免创建不必要的对象_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > Effective Java学习之——避免创建不必要的对象

Effective Java学习之——避免创建不必要的对象

 2013/9/27 21:47:36  sungang_1120  程序员俱乐部  我要评论(0)
  • 摘要:一般来说,最好的重用对象而不是在每次需要的使用就创建一个相同的对象出来。重用方式既快速、又流行。如果对象是不可变的(immutable),它就始终可以被重用。作为一个极端的反面例子,考虑下面的语句:Strings=newString("hello");该语句每次执行时都会创建一个新的String对象实例,但是这些创建的对象的动作全都是不必要的。传递给String构造的参数("hello")本身就是一个String实例,功能方面等同于构造器创建的所有对象
  • 标签:创建 学习 Java

? ? ? 一般来说,最好的重用对象而不是在每次需要的使用就创建一个相同的对象出来。重用方式既快速、又流行。如果对象是不可变的(immutable),它就始终可以被重用。

?

作为一个极端的反面例子,考虑下面的语句:

?

class="java" name="code">String s = new String("hello");

? ? ? ?该语句每次执行时都会创建一个新的String对象实例,但是这些创建的对象的动作全都是不必要的。传递给String构造的参数("hello")本身就是一个String实例,功能方面等同于构造器创建的所有对象。如果这种用法是在一个循环中或者在一个频繁被调用的方法中,就会被创建成千上万的不必要的String实例对象。

?

?

?改进后的版本如下所示:

?

String s = "hello";

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

?

?

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

?

? ? ? 除了重用不可变的对象之外,也可以重用那些已知不会被修改的对象。下面是一个比较微妙、也比较常见的反面实例,其中涉及到可变的Date对象,它们的值一旦计算出来之后就不再变化。这个类建立了一个模型:其中有一个人,并有一个isBabyBoomer方法,用来检验这个人是否是一个"baby boomer(生育高峰期出生的小孩)",换句话说,就是检查这个人是否出身在那年到那年期间的。

?

public class Person {
	private final Date birthday = null;
	
	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 birthday.compareTo(boomStart) >= 0 && birthday.compareTo(birthday) < 0;
	}
}

? ? ?isBabyBoomer每次执行的时候,都会创建一个Calendar、一个TimeZone和两个Date实例,这是不必要的。下个版本用一个静态初始化器(initializer),避免这样效率低的情况:

?

?

public class Person{
	private final Date birthday = null;
	private static Date BOOM_START = null;
	private static Date BOOM_END = null;
	
	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 birthday.compareTo(BOOM_START) >= 0 && birthday.compareTo(BOOM_END) < 0;
	}
}

? ? ? 改进后的Person类只在初始化的时候创建了Calendar、TimeZone和Date实例一次,而不是在每次调用的时候都创建这些实例。如果isBabyBoomer方法频繁被调用,这种方法将会显著的提高性能。

?

?

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

?

? ? ? 例如,Map接口的keySet方法返回该Map对象的Set视图,其中包含该Map中所有的键(key)。粗看起来,好像每次调用keySet都应该创建了一个新的Set实例,但是,对于一个给定的Map对象,实际上每次调用keySet都返回同样的Set实例。虽然被返回的Set实例一般是可改变的,但是所有返回的对象在功能上是等同的:当其中一个返回的对象发生了变化的时候,所有其他的返回对象也要发生变化,因为他们是由同一个Mao实例支撑的。虽然创建keySet视图对象的多个实例并无害处,却也不是有必要的。

?

? ? ? 在JAVA1.5发行版本中,有一种创建多余对象的新方法,称作自动装箱(autoboxing),他允许程序员将基本类型和装箱基本类型混用,按需求自动装箱和拆箱。自动装箱使得基本类型和装箱基本类型之间的差别变得模糊起来,但是并没有完全消除。他们在语义上还有着微妙的差别,在性能上也有着比较明显的差别。考虑下面程序,他计算所有int正值的总和。为此,程序必须使用long算法,因为int不够大,无法容纳所有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);//2305843005992468481
	}

?

? ? ?程序算出的答案是正确的,但是比实际情况更慢一些,只因为打错了一个字符。变量sum被声明为Long而不是long,意味着程序构造大约2的31次方的多余Long实例(大约每次往Long sum中增加long时构造一个实例)。将sum的声明改为long,很明显效率会快很多。注:要优先使用基本类型而不是包装类,要当心无意识的自动装箱?

?

? ? ?以上就是一些避免创建对象给程序带来不必要的性能上的危害,我们应该尽可能的避免创建对象。相反,由于小对象的构造器只做很少的量的显示工作,所以,小对象的创建和回收动作是非常廉价的,特别是在现代的JVM实现上更是如此。通过创建附件的对象,提升程序的清晰性,简洁性和功能性,通常是件好事。

?

? ? ? 反之,通过维护自己的对象池(Object pool) 来避免创建对象的并不是一种好的做法,除非池中的对象是非常重量级的。真正正确的使用对象池的典型对象示例就是数据库连接池。建立数据库连接的代价是非常昂贵的,因此重用这些对象非常有意义。而且,数据库的许可可能限制你只能使用一定数量的连接。但是,一般而言,维护自己的对象池必定会把代码弄得很乱,同时增加内存的占用,并且还会损害性能。现代的JVM实现具有高度优化的垃圾回收器,其性能很容易就会超过轻量级的对象池的性能。

发表评论
用户名: 匿名