×

消息

EU e-Privacy Directive

This website uses cookies to manage authentication, navigation, and other functions. By using our website, you agree that we can place these types of cookies on your device.

View e-Privacy Directive Documents

You have declined cookies. This decision can be reversed.

避免创建不必要的对象

最常见的例子是String,一个小小的String对象看起来不起眼,但在复杂的大型系统中,如果不恰当创建,可能会带来极大的性能开销,比如:

String s = new String("stringette"); // DON'T DO THIS!

这行代码的问题是,作为参数的"stringette"本身已经是所需要的String对象了,菜鸟程序员却调用一个String类的构造函数来再次构造它。这行代码每执行一次,就会在栈里面建立一个一模一样的"stringette"对象。在高频执行的环境下,内存开销会非常大,甚至引起stack overflow。正确的做法是:

String s = "stringette";

这样建立的"stringette"对象,会由虚拟机的string pool机制管理,并存储于堆内存中。如果其他代码再用到这个字符串,同一个对象将被重用。比如:

String s1 = "stringette";
String s2 = "stringette";
String s3 = "stringette";

所有的s1,s2,s3,包括上面的s,实际上都指向内存中同一个对象,用==判断,他们全是相同的。所以不管多少次调用或重用,都只占用这一个对象的空间。但要注意,这种对象池思想,只应用于immutable对象,也就是说,可以这样重用的对象必须一经创建,就不再可以被修改。另一个类似的例子是Boolean对象的创建,推荐的方法是用静态factory方法:Boolean.valueOf(String),而不是Boolean(String),原理可以参看item 1

对于mutable对象,如果创建后不再改变,仍需要考虑对象的重用,以减少性能开销:

public class Person {
	private final Date birthDate;
	// Other fields, methods, and constructor omitted
	
	// DON'T DO THIS!
	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对象,而且在多次调用中,建立的对象都是完全一样的,此时就需要把这样的对象重用起来:

class Person {
	private final Date birthDate;
	// Other fields, methods, and constructor omitted
	
	/**
	* 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;
	}
}

修改后的代码中,Calendar对象只被创建一次,在之后的isBabyBoomer调用中被重用。作为性能比较,原始代码执行1000万次耗时32000毫秒,而修改后的代码只用130毫秒,性能提升了250倍!类似的例子还有adapter设计模式中的后台对象,也应避免重复创建。

另一个产生冗余对象的场景是Java5的autoboxing特性:

// 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,所以循环中每次sum增值都会由autoboxing创建一个新的Long对象。循环结束时,这样的对象建立了221个!为避免这样的问题,要尽可能优先使用原始数据类型(int,long,double等等),而不是对象封装的数据类型(Integer,Long,Double等),还要留意autoboxing的出现

注意,本节内容不可过度应用,尤其是对象池思想,仅在对象创建开销非常大的情况下适用(比如管理数据库连接时),否则由于pool管理的复杂度,往往得不偿失。

提交评论


安全码
刷新