第1条:考虑用静态工厂方法代替构造器
(Consider static factory methods instead of constructors)
静态工厂方法的优势:
- 静态工厂方法与构造器不同的第一大优势在于,它们有名称。如果构造器的参数本身没有确切地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用。
- 静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象。 笔者注:缓存,重复利用
- 静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。 笔者注: 更大的灵活性,构成service provider framework的基础
- 静态工厂方法的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。 笔者注:得益于类型推导
静态工厂方法的缺点:
- 静态工厂类如果不含public或者protected构造器,就不能被子类化。
它们与其他的静态方法实际上没有任何区别。 笔者注: api文档没像constructor那样明确标注 - 静态工厂方法的惯用方法名:
valueOf
of
getInstance
newInstance
getType
newType
第2条:遇到多个构造器参数时要考虑用builder模式
(Consider a builder when faced with many constructor parameters)
一个类的大部分字段为可选字段的场合,构建类有三种模式:
分别是重叠构造器,Javabean模式和Builder模式。
重叠构造器的示例代码:
// Telescoping constructor pattern - does not scale well! - Pages 11-12
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional
private final int sodium; // (mg) optional
private final int carbohydrate; // (g) optional
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
public static void main(String[] args) {
NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);
}
}
一长串类型相同的参数会导致一些微妙的错误。如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会出错,但是程序在运行时会出现错误的行为。
Javabean模式:调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数。
缺点:构造过程中无法保证一致性;阻止了把类做成不可变的可能。
Javabean模式示例代码:
// JavaBeans Pattern - allows inconsistency, mandates mutability - Pages 12-13
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // " " " "
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}
如果类的构造器或者静态工厂中具有多个参数,设计这种类时, Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与使用传统的重叠构造器模式相比,使用Builder模式的客户端代码将更易于阅读和编写,构建器也比JavaBeans更加安全。
Builder模式示例代码:
// Builder Pattern - Pages 14-15
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val)
{ calories = val; return this; }
public Builder fat(int val)
{ fat = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
}
}
第3条:用私有构造器或者枚举类型强化Singleton属性
(Enforce the singleton property with a private constructor or an enum type)
在Java 1.5发行版本之前,实现Singleton有两种方法。
第一种方法中,公有静态成员是个final字段:
// Singleton with public final field - Page 17
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
第二种方法中,公有的成员是个静态工厂方法:
// Singleton with static factory - Page 17
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.getInstance();
elvis.leaveTheBuilding();
}
}
从Java 1.5发行版本起,实现Singleton还有第三种方法。只需编写一个包含单个元素的枚举类型,该方法已经成为实现Singleton的最佳方法。
// Enum singleton - the preferred approach - page 18
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
注意:客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。
第4条:通过私有构造器强化不可实例化的能力
(Enforce noninstantiability with a private constructor)
企图通过将类做成抽象类来强制该类不可被实例化,这是行不通的!
我们只要让这个类包含私有构造器它就不能被实例化了:
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
}
副作用,它使得一个类不能被子类化。
第5条:避免创建不必要的对象
(Avoid creating unnecessary objects)
Person 类的isBabyBoomer方法每次被调用时都会产生一个Calendar对象,一个Timezone对象和两个Date对象,性能开销很大。
// Creates lots of unnecessary duplicate objects - page 20-21
import java.util.*;
public class Person {
private final Date birthDate;
public Person(Date birthDate) {
// Defensive copy - see Item 39
this.birthDate = new Date(birthDate.getTime());
}
// Other fields, methods 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;
}
}
正确的做法应该是:
// Doesn't creates unnecessary duplicate objects - page 21
import java.util.*;
class Person {
private final Date birthDate;
public Person(Date birthDate) {
// Defensive copy - see Item 39
this.birthDate = new Date(birthDate.getTime());
}
// Other fields, methods
/**
* 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;
}
}
要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
以下代码中的Long sum改为long sum将显著提升性能。
public class Sum {
// 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);
}
}
第6条:消除过期的对象引用
(Item 6: Eliminate obsolete object references)
一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。
内存泄漏的另一个常见来源是缓存。
内存泄漏的第三个常见来源是监听器和其他回调。
以下示例代码的pop()方法中,应考虑增加elements[size] = null;
// Can you spot the "memory leak"?
import java.util.*;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
/**
* Ensure space for at least one more element, roughly
* doubling the capacity each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
第7条:避免使用终结方法
(Item 7: Avoid finalizers)
终结方法(finalizer) 通常是不可预测的,也是很危险的.一般情况下是不必要的。
使用终结方法有一个非常严重的(Severe)性能损失。
如果类的对象中封装的资源(例如文件或者线程)确实需要终止, 只需提供一个显式的终止方法,显式的终止方法通常与try-finally结构结合起来使用,以确保及时终止。