Effective Java学习笔记 --- 创建与销毁对象02

使用枚举类型强化Singleton属性

Singleton指的是仅被实例化一次的类,即我们常说的单例.我们可以通过枚举类型来实现单例.

1
2
3
4
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){......)
}

这种方法是最简单的方法,而且它提供了序列化的机制,绝对防止多次实例化,避免通过反射利用无参构造器获取实例,这种方法可能是目前实现单例的最佳方法.

通过私有构造器强化不可实例化

有些类并不希望被实例化,例如Java内置的工具类,它们只提供一些静态的工具方法.实例化并无任何的意义,当这种类没有显示的构造器时,编译器会自动提供一个公有的无参构造器,这让就会有被实例化的危险了.为了保证不被实例化,可以提供一个私有的构造器,这让就可以避免外部实例化该类.

1
2
3
4
5
6
public class UtilityClass{
private UtilityClass(){
throw new AssertionError();
}
// other method
}

上面的代码中在私有构造器中抛出的AssertionError可以避免在类的内部被实例化.这种方法的缺点就是无法子类化.

避免创建不必要的对象

尽量的重用对象,而不是每次都创建新的对象.下面是一个反面例子:

1
String s = new String("stringette");

这个语句每次执行都会创建一个新的String对象,而且作为参数的"stringette"本身就是一个String实例,若把该语句放到一个循环当中,那将会创建出大量相同的对象.我们应该使用下面的语句代替:

1
String s = "stringette";

这样只用了一个String实例,而且在同一台虚拟机运行,只要包含相同的字符串字面常量,对象都会被重用.对于同时提供构造器静态工厂方法的不可变类,应该优先选择静态工厂方法,避免创建不必要的对象.例如优先使用Boolean.valueOf(String)而不是使用Boolean(String).

重用那些已知不会被修改的可变对象.下面我们举一个例子,我们先看反面的代码

1
2
3
4
5
6
7
8
9
10
11
12
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 = gtmCal.getTime();
gtmCal.set(1965,Calendar.JANUARY,1,0,0,0);
Date boomEnd = gtmCal.getTime();
return birthDate.compareTo(boomStart) >=0 && birthDate.compareTo(boomEnd) < 0;
}
}

上面的代码中,isBabyBoomer()每次调用都会新建一个Calendar,一个TimeZone和两个Date实例,而这些实例的值都是确定的,不会被修改,这样的代码效率会大大的降低.下面我们用静态初始化块对代码进行优化.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 = gtmCal.getTime();
gtmCal.set(1965,Calendar.JANUARY,1,0,0,0);
BOOM_END = gtmCal.getTime();
}
public boolean isBabyBoomer(){
return birthDate.compareTo(BOOM_START) >=0 &&
birthDate.compareTo(BOOM_END) < 0;
}
}

改进后的代码,只在初始化时创建Calendar,TimeZone,和Date实例一次,大大的提高了效率.

优先使用基本数据类型,而不是相对应的装箱基本类型.因为创建对象的代价远远比使用基本数据类型的代价大.
这里提倡我们应该尽可能的避免创建对象,但是对于小对象的构造器只做少量的工作,而且会提示程序的清晰和简洁.通过维护现有的对象池而避免创建对象并不是一种好的做法,除法对象池中的对象是重量级的,而且维护对象池会导致代码难以阅读,有时还会损害性能.所有我们要通过自己的衡量来决定是否要创建对象.