1. 考虑用静态工厂方法代替构造函数
静态工厂方法优点:具有名字;每次调用时,不要求必须创建新对象;可以返回对象。
实质就是静态方法,可以方便的调用。缺点是不能被实例化。
//定义静态方法
Public class StaticTest {
public static String getResults(String name) {
return name;
}
}
//调用
String name = StaticTest.getResults("tingor");
服务提供框架: 有一个Properties文件,每个Key对应一个Class,框架用来注册一个类,然后实例化。
public abstract class Foo {
private static Map implementations = null;
private static synchronized void initMapIfNecessary() {
if (implementations == null)
implementations = new HashMap();
}
public static Foo getInstance(String key) {
initMapIfNecessary();
Class c = (Class) implementations.get(key);
if (c == null)
return new DefaultFoo();
try {
return (Foo) c.newInstance();
} catch (Exception e) {
return new DefaultFoo();
}
}
public static void main(String[] args) {
System.out.println(getInstance("NonexistFoo"));
}
}
public class DefaultFoo extends Foo {}
2. 使用私有构造函数强化Singleton属性
singleton只能被实例化一次,通常用来代表唯一性的系统组件。
方法一: 饥饿加载 - 性能稍微优先
public class Singleton() {
public static final MySingleton instance = new MySingleton();
private Singleton() {}
public static MySingleton getInstance() {
return INSTANCE;
}
}
方法二: 懒加载
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为了维护singleton性,在序列化的时候需要提供一个readResolve方法,以便对象在反序列化的时候,会创建一个新的实例
public class Singleton() {
public static final Singleton instance = new MySingleton();
private MySingleton() {}
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
3. 通过私有构造函数强化不可实例化的能力
不能实例化一个类,一般用于工具类的范围。
public class UtilityClass {
private UtilityClass() {}
}
4. 避免创建重复的对象
4.1 不要重复创建同一个对象
String s = new String("silly"); //Don't do this
String s = "silly"; //should do this
4.2 把只需要创建一次的局部变量变成final静态域
//错误做法(每次isBabyBoomer被调用的时候,都会创建Calendar和TimeZone,以及两个Date实例)
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;
}
}
//改进之后
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);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(boomStart) >=0 && birthDate.compareTo(boomEnd) < 0;
}
}
5. 消除过期的对象引用
如果对象一直不被回收,那么很容易引起OutOfMemoryError的错误。
//Stack中,先增长,然后收缩,那么从栈弹出的对象将不会被回收;另一个例子在缓存,解决的办法是使用WeakHashMap
public class Stack {
private Object[] elements;
private int size = 0;
public Stack(int initialCapacity) {
this.elements = new Object[initialCapacity];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size) {
Object[] oldElements = elements;
elements = new Object[2 * elements.length + 1];
System.arraycopy(oldElements, 0, elements, 0, size);
}
}
}
//pop方法修改版本
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; //消除旧的引用
return result;
}
6. 避免使用终结函数
6.1 终结函数并不能保证能够执行,如果非要使用,那么首要考虑显示终止方法
Foo foo = new Foo(...);
try {
//Do what mush be done with foo
} finally {
foo.terminate(); //foo自带的外在终止方法,还有例如数据库的关闭连接方法
}
6.2 如果一类有一个终结函数,并且一个子类改写了终结函数,那么子类的终结函数必须要调用父类的终结函数
protected void finalize() throws Throwable {
try {
//Finalize subclass state
} finally {
super.finalize();
}
}
6.3 如果一个子类改写了超类的终结函数,但忘了手工调用超类的终结函数,那么使用终结函数守卫者(finalizer guardian)来实现其终结,注意该终结函数放在一个匿名的类中
public class Foo {
private final Object finalizerGuardian = new Object() {
protected void finalize() throws Throwable {
//finalize outer Foo object
}
}
}
7. 在改写equals的时候请遵守通用约定
当一个类有自己特有的逻辑相等原则,而超类也没有改写,则需要改写Object.equals
自反性:x.equals(x)一定为true
对称性:y.equals(x)返回true, x.equals(y)一定也为true
传递性:x.equals(y)为true,y.equals(z)为true,那么x.equals(z)一定为true
一致性:多次调用x.equals(y)要么一直返回true,要么一致返回false
非空性:非空x,x.equals(null)一定返回false
public boolean equals(Obejct otherObject) {
if (this == otherObject) return true; //检测this与otherObject是否引用同一个对象
if (otherObject == null) return false; //检测otherObject是否为null
if (getClass() != otherObject.getClass)
return false; //比较this与otherObject是否属于同一个类
if (!otherObject instanceof thisClass)
return false; //该方法可以替代上一行判断
Employee other = (Employee)otherObject; //将otherObject转换为相应的类型
return name.equals(other.name) && salary == other.salary; //对所有域进行比较
}
8. 改写equals时总是要改写hashCode
private volatile int hashCode = 0; //延迟初始化
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 37 * result + areaCode;
}
return hashCode;
}
9. 总是要改写toString
主要用于输出的程序便于调试
public String toString() {
return getClass().getName() + "[name=" + name + ",salary=" +
salary + ",hireDay=" + hireDay + "]";
}
10. 谨慎地改写clone
对于该条目,我认为最好不使用clone方法。
11. 考虑实现Comparable接口
compareTo方法允许简单的相等比较,也允许执行顺序比较,一般用于Arrays等数据结构
Arrays.sort(a);
12. 使类和成员的可访问能力最小化
好的设计,应该隐藏内部数据和具体细节
13. 支持非可变性
例如: String,BigInteger都是非可变类
非可变类,实例不能被修改,遵循5条规则
不要提供修改对象的方法
保证没有子类改写的方法
使所有的域都是final
使所有的域都成为私有的
保证对于可变组件的互斥访问
非可变对象本质上是安全的,所以不要求同步,这样类定义为final,就可以被多个线程共享
14. 复合优先于继承
包装类即装饰器的使用
15. 要么专门为继承而设计,并给出文档说明,要么禁止继承
16. 接口优于接口类
17. 接口只是被用于定义类型
在接口中,最好不要定义常量
18. 优先考虑静态成员类
声明的成员类不要求访问外围实例,那么应该加入static修饰符
嵌套类: 静态成员类,非静态成员类,匿名类,局部类
19. 用类代替结构
20. 用类层次代替联合
21. 用类代替enum结构
在Java5中,又加入了enum这种结构
//类型安全枚举
public class Suit {
private final String name;
private Suit(String name) {
this.name = name;
}
public String toString() {
return name;
}
public static final Suit CLUBS = new Suit("clubs");
public static final Suit DIAMONDS = new Suit("diamonds");
public static final Suit HEARTS = new Suit("hearts");
public static final Suit SPADES = new Suit("spades");
}
22. 用类和接口来代替函数指针
23. 检查参数的有效性
应该在抛出空指针或者其他不可获知异常之前,进行参数检查
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0)
throw new ArithmenticException("Modulus not positive");
}
24. 需要时使用保护性拷贝
//原始类
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compartTo(end) > 0)
throw new IllegalArgumentException(start + " after " + end);
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
//修改Date
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); //已经修改了时间
//重构之后的类
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (start.compartTo(end) > 0)
throw new IllegalArgumentException(start + " after " + end);
}
public Date start() {
return (Date)start.clone();
}
public Date end() {
return (Date)end.clone();
}
25. 谨慎设计方法的原型
谨慎选择方法的名字
不要过于追求提供便利的方法
避免长长的参数列表(一般为3个): 有两个方法来避免:把一个方法分解成多个方法;创建辅助类
对于参数类型,优先使用接口而不是类
谨慎使用函数对象
26. 谨慎使用重载
27. 返回零长度数组而不是null
28. 为所有导出的API元素编写文档注释
29. 将局部变量的作用域最小化
局部变量应该在第一次使用它的地方声明,而不要过早在前面声明
如果循环终止后循环变量不再需要,那么for循环应该优先于while循
30. 了解和使用库
java.lang,java.util,java.io
31. 如果要求精确的答案,请避免使用float和double
尤为不合适货币计算,可以考虑BigDecimal
32. 如果其他类型更合适,则尽量避免使用字符串
33. 了解字符串连接的性能
使用StringBuffer或者StringBuilder代替
34. 通过接口引用对象
List subscribers = new ArrayList();
如果没有接口,那么就用基类来引用
35. 接口优于反射机制
36. 谨慎地使用本地方法
37. 谨慎地进行优化
努力避免那些限制性能的设计决定,考虑你的API设计决定的性能后果
38. 遵守普遍接受的命名惯例
39. 只针对不正常的条件才使用异常
40. 对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常
41. 避免不必要地使用被检查的异常
42. 尽量使用标准的异常
IllegalArgumentException: 参数的值不合适
IllegalStateException: 对于这个方法调用而言,对象状态不合适
NullPointerException: 在null被禁止的情况下参数值为null
IndexOutOfBoundException: 下标越界
ConcurrentModificationException: 在禁止并发修改的情况下,对象检测到并发修改
UnsupportOerationException: 对象不支持客户请求的方法
43. 抛出的异常要适合于相应的抽象
可以把异常进行包装,转译成容易理解的异常
44. 每个方法抛出的异常都要有文档
45. 在细节消息中包含失败-捕获异常
可以在异常中加入失败的消息,关键字符
46. 努力使失败保持原子性
//在其他动作之前,就进行size检查
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; //消除旧的引用
return result;
}
47. 不要忽略异常
48. 对共享可变数据的同步访问
49. 避免过多的同步
50. 永远不要在循环的外面调用wait
一般使用notifyAll()
51. 不要依赖于线程调度器
52. 线程安全性的文档化
53. 避免使用线程组
54. 谨慎地实现Serializable
实现了Serializable的代价是,一旦一个类发布,改变这个类的实现的灵活性将降低
为了继承而设计的类和内部类应该很少实现Serializable
55. 考虑使用自定义的序列化形式
56. 保护性地编写readObject方法
57. 必要时提供一个readResolve
对于Singleton需要