您的位置:首页 > 其它

避免创建不必要的对象

2017-08-29 17:31 423 查看
 看Effective java 略有所感,首先不要错觉的根据标题认为“创建对象的代价非常昂贵,我们应该要尽可能的避免创建对象”。相反,由于小对象的构造器只能做很少量的显示工作,所以,小对象的创建和回收动作是非常廉价的,特别是在现代的JVM实现上更是如此。通过创建附加小对象,提升程序的清晰性,简洁性和功能性,这通常是件好事情。

        那接下来我们看看一个如何避免创建不必要的对象的代码,首先看看一个可能工作中经常会写的代码:这个类建立一个模型,其中有一个人,并有一个isBabyBoomer方法,用来检验这个人是否为一个“baby boomer”生育高峰期出生的小孩,换句话说,就是检验这个人是否出生于1946年至1964年期间

        

public class Person {

private final Date birthDate;

public Person(Date birthDate){
this.birthDate = 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(1965, 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;

public Person(Date birthDate){
this.birthDate = birthDate;
}

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;
}
}    


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

最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象。

如果对象是不可变(immutable),它就始终可以被重用

String s = new String("stringette");

每次执行上面的语句都会创建一个新的String实例。因为"stringette"本身就是一个String实例,这和构造器创建出来的新对象功能方面完全相同。如果频繁调用这一方法,就会创建成千上万不必要的String实例。

String s = "stringette";

这样就保证了只有一个String实例,而不是每次执行都创建一个新的实例。并且同一台JVM只要字符串字面常量相同,对象就会被重用。

同时提供静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器以避免创建不必要对象。例如静态工厂方法Boolean.valueOf(String)回返回一个固定的Boolean实例,而构造器Boolean(String)在每次被调用都会创建一个新的对象。

已知可变对象不会被修改,则也可以重用

public class Person {
private final Date birthDate;

public Person(Date birthDate) {
this.birthDate = 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(1965,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;

public Person(Date birthDate) {
this.birthDate = 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(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;
}
}

这样Person类只在初始化时创建Calendar、TimeZone和Date实例一次,而不是每次调用isBabyBoomer的时候都创建这些实例。除了性能的提升,代码的含义也更加清晰——boomStart和boomEnd从局部变量改为final静态域,表明这些数据被当成常量对待,使得代码更易于理解。

自动装箱、拆箱可能会创建多余对象。

public static void main(String[] args){
Long sum=0L;
for(long i = 0; i< Integer.MAX_VALUE; i++){
sum+=i;
}
}

sum被声明为Long而不是long,这样程序构造了2^31个多余的Long实例。所以这里结论是要优先使用基本类型而不是装箱类型,当心无意识自动装箱。

注意

不要认为“创建对象代价非常昂贵”。实际上小对象的构造器只做很少量的显式工作,所以小对象的创建和回收动作是非常廉价的,现代JVM上更是如此。通过创建附加的对象,提升程序的清晰性、简洁性和功能性。

反之自己维护对象池很麻烦,除非对象非常重量级,比如数据库连接池

作者:陈继科

链接:http://www.jianshu.com/p/6b7cb0059e52

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: