第1條:考慮用靜態(tài)工廠方法代替構(gòu)造器
(Consider static factory methods instead of constructors)
靜態(tài)工廠方法的優(yōu)勢:
- 靜態(tài)工廠方法與構(gòu)造器不同的第一大優(yōu)勢在于食拜,它們有名稱队腐。如果構(gòu)造器的參數(shù)本身沒有確切地描述正被返回的對象充易,那么具有適當(dāng)名稱的靜態(tài)工廠會更容易使用稿静。
- 靜態(tài)工廠方法與構(gòu)造器不同的第二大優(yōu)勢在于,不必在每次調(diào)用它們的時候都創(chuàng)建一個新對象滞诺。 筆者注:緩存淋叶,重復(fù)利用
- 靜態(tài)工廠方法與構(gòu)造器不同的第三大優(yōu)勢在于,它們可以返回原返回類型的任何子類型的對象。 筆者注: 更大的靈活性癣缅,構(gòu)成service provider framework的基礎(chǔ)
- 靜態(tài)工廠方法的第四大優(yōu)勢在于屡立,在創(chuàng)建參數(shù)化類型實例的時候焚刺,它們使代碼變得更加簡潔蔓姚。 筆者注:得益于類型推導(dǎo)
靜態(tài)工廠方法的缺點:
- 靜態(tài)工廠類如果不含public或者protected構(gòu)造器,就不能被子類化。
它們與其他的靜態(tài)方法實際上沒有任何區(qū)別。 筆者注: api文檔沒像constructor那樣明確標(biāo)注 - 靜態(tài)工廠方法的慣用方法名:
valueOf
of
getInstance
newInstance
getType
newType
第2條:遇到多個構(gòu)造器參數(shù)時要考慮用builder模式
(Consider a builder when faced with many constructor parameters)
一個類的大部分字段為可選字段的場合,構(gòu)建類有三種模式:
分別是重疊構(gòu)造器瓶埋,Javabean模式和Builder模式挤悉。
重疊構(gòu)造器的示例代碼:
// 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);
}
}
一長串類型相同的參數(shù)會導(dǎo)致一些微妙的錯誤畏梆。如果客戶端不小心顛倒了其中兩個參數(shù)的順序,編譯器也不會出錯浴捆,但是程序在運行時會出現(xiàn)錯誤的行為。
Javabean模式:調(diào)用一個無參構(gòu)造器來創(chuàng)建對象忿族,然后調(diào)用setter方法來設(shè)置每個必要的參數(shù),以及每個相關(guān)的可選參數(shù)碉考。
缺點:構(gòu)造過程中無法保證一致性;阻止了把類做成不可變的可能报腔。
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);
}
}
如果類的構(gòu)造器或者靜態(tài)工廠中具有多個參數(shù)拄查,設(shè)計這種類時役拴, Builder模式就是種不錯的選擇,特別是當(dāng)大多數(shù)參數(shù)都是可選的時候矢腻。與使用傳統(tǒng)的重疊構(gòu)造器模式相比初嘹,使用Builder模式的客戶端代碼將更易于閱讀和編寫,構(gòu)建器也比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條:用私有構(gòu)造器或者枚舉類型強化Singleton屬性
(Enforce the singleton property with a private constructor or an enum type)
在Java 1.5發(fā)行版本之前洒琢,實現(xiàn)Singleton有兩種方法谭网。
第一種方法中愉择,公有靜態(tài)成員是個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();
}
}
第二種方法中,公有的成員是個靜態(tài)工廠方法:
// 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發(fā)行版本起旧乞,實現(xiàn)Singleton還有第三種方法。只需編寫一個包含單個元素的枚舉類型大磺,該方法已經(jīng)成為實現(xiàn)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方法流济,通過反射機制調(diào)用私有構(gòu)造器。
第4條:通過私有構(gòu)造器強化不可實例化的能力
(Enforce noninstantiability with a private constructor)
企圖通過將類做成抽象類來強制該類不可被實例化腌闯,這是行不通的绳瘟!
我們只要讓這個類包含私有構(gòu)造器它就不能被實例化了:
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
}
副作用,它使得一個類不能被子類化姿骏。
第5條:避免創(chuàng)建不必要的對象
(Avoid creating unnecessary objects)
Person 類的isBabyBoomer方法每次被調(diào)用時都會產(chǎn)生一個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;
}
}
正確的做法應(yīng)該是:
// 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;
}
}
要優(yōu)先使用基本類型而不是裝箱基本類型姨丈,要當(dāng)心無意識的自動裝箱。
以下代碼中的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)
一般而言蟋恬,只要類是自己管理內(nèi)存,程序員就應(yīng)該警惕內(nèi)存泄漏問題趁冈。
內(nèi)存泄漏的另一個常見來源是緩存歼争。
內(nèi)存泄漏的第三個常見來源是監(jiān)聽器和其他回調(diào)拜马。
以下示例代碼的pop()方法中,應(yīng)考慮增加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條:避免使用終結(jié)方法
(Item 7: Avoid finalizers)
終結(jié)方法(finalizer) 通常是不可預(yù)測的沐绒,也是很危險的.一般情況下是不必要的俩莽。
使用終結(jié)方法有一個非常嚴重的(Severe)性能損失。
如果類的對象中封裝的資源(例如文件或者線程)確實需要終止乔遮, 只需提供一個顯式的終止方法扮超,顯式的終止方法通常與try-finally結(jié)構(gòu)結(jié)合起來使用蹋肮,以確保及時終止。