1、考慮用靜態工廠方法代替構造器 類的一個實例,通常使用類的公有的構造方法獲取。也可以為類提供一個公有的靜態工廠方法(不是設計模式中的工廠模式)來返回類的一個實例。例如: 使用靜態工廠方法代替構造器的優勢: 靜態工廠方法有名稱,更易讀。靜態工廠方法能夠使用方法名稱進行自註釋,來描述被返回的對象。例如 ...
1、考慮用靜態工廠方法代替構造器
類的一個實例,通常使用類的公有的構造方法獲取。也可以為類提供一個公有的靜態工廠方法(不是設計模式中的工廠模式)來返回類的一個實例。例如:
//將boolean類型轉換為Boolean類型
public static valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
使用靜態工廠方法代替構造器的優勢:
靜態工廠方法有名稱,更易讀。靜態工廠方法能夠使用方法名稱進行自註釋,來描述被返回的對象。例如:
BigInteger.probablePrime
的靜態方法表示返回的BigInteger為素數。當一個類需要多個帶有相同簽名(參數列表僅在參數類型的順序上有所不同)的構造器時,應使用靜態工廠方法並且慎重的選擇名稱以便突出它們的區別。靜態工廠方法能夠為重覆的調用返回相同的對象。將創建好的對象緩存起來,重覆利用。
靜態工廠方法可以返回原返回類型的任何子類型的對象。例如:在基於介面的框架(通過介面來引用對象)中,為了隱藏類的實現,通常會使用API返回對象的實例。由於介面中不能有靜態方法,通常把靜態工廠方法放在實現了介面的不可實例化類(private構造函數)中。
創建參數化類型實例時,靜態工廠方法使代碼變的更簡潔。(java 8加入泛型的推導後,優勢不再)
Map<String, List<String>> map = new HashMap<String, List<String>>(); // java 1.7之前必須這樣寫,前後兩次都要寫泛型列表
Map<String, List<String>> map = new HashMap<>(); // java 1.8可以這樣寫
//靜態工廠方法
//HashMap類中加入
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
//使用
Map<String, List<String>> map = HashMap.newInstance();
缺點:
- 類如果不含有public或protected型的構造器,則不能被繼承。
2、遇到多個構造器參數時考慮用構建器
當有多個構造器參數(其中一些為可選參數)時,代碼的編寫通常有幾種方式:
使用重疊的構造器,根據可選參數提供不同的構造器。
使用JavaBean模式:先調用一個無參的構造器來創建對象,然後使用setter方法來設置必要的參數。
當參數很多時,使用重疊的構造器代碼的編寫將很繁瑣,難以閱讀,同時調用時容易出錯。而是用JavaBean模式,因為對象的構造過程被分到了幾個調用中,在構造過程中JavaBean處於不一致狀態。試圖使用(特別是在多線程中)不一致狀態的對象,將導致錯誤並且很難調試。並且JaveBean模式阻止了將類做成不可變的可能。
這時可以考慮使用構建器(Builder模式),不直接生成想要的對象,而是通過Builder對象來構造對象。
代碼:
public class AlertDialog {
private final String title;
private final String message;
private final boolean cancelable;
private AlertDialog(Builder builder) {
this.title = builder.title;
this.message = builder.message;
this.cancelable = builder.cancelable;
}
public static class Builder {
private final String title;
private String message;
private boolean cancelable;
public Builder(String title) {
this.title = title;
}
public Builder setMessage(String message) {
this.message = message;
return this;
}
public Builder setCancelable(boolean cancelable) {
this.cancelable = cancelable;
return this;
}
public AlertDialog create() {
return new AlertDialog(this);
}
}
public String toString() {
return "title: " + title + ", message: " + message + ", cancelable: " + cancelable;
}
public static void main(String[] args) {
AlertDialog dialog = new AlertDialog.Builder("地點")
.setMessage("知識的荒漠")
.setCancelable(true).create();
System.out.println(dialog);
}
}
Builder模式的優點:
比JavaBean更安全,比重疊的構造器更易於閱讀和編寫
適用於參數較多,且大多數參數可選的情況
3、用私有構造器或枚舉類型強化Singleton屬性
實現Singleton的幾種方式:
方式一:餓漢方式,線程安全
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
這種方式基於classloader機制避免了線程同步的問題。但singleton在類被裝載時立即實例化,沒有實現類的延時載入(lazy loading)。另外這種方式可以通過反射機制(藉助Class對象的setAccessible方法)來生成對象。
方式二:懶漢方式,線程不安全
public class Singleton {
private static final Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
這種方式實現了類的延時載入,但致命的是多線程下不安全。
方式三:雙重校驗鎖,線程安全
public class Singleton {
private volatile static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
方式四:枚舉,線程安全,可避免反序列化時重新創建對象,推薦使用
public enum Singleton {
INSTANCE;
private Singleton() {}
.....
}
//使用
Singleton single = Singleton.INSTANCE;
這種方式可自由序列化,可有效避免反序列化時創建多個對象。保證只有一個實例(即使反射機制也無法多次實例化一個枚舉量),線程安全。缺點是使用的人較少(java1.5後才有enum類型),失去了類的一些特性。單元素的枚舉型是實現Singleton的最佳方式
4、通過私有構造器強化不可實例化的能力
編寫一些只包含靜態方法或靜態域的類(工具類)時,可以使用private構造器來使類不能被實例化。這些類不能被繼承。(因為子類的構造器都要顯示或隱式調用父類的構造器)。自己編寫的工具類要使用這種方法
public class UtilityClass {
private UtilityClass() {
throw new AssertionError(); //斷言,確保不會在類內部被調用
}
.....
}
5、避免創建不必要的對象
重用對象而不是每次需要時創建一個相同的新對象,這可以顯著程式提高性能。對於不可變的對象應始終重用,對於可變的對象應儘量重用。
例如:
String str1 = "abc";
String str2 = new String("abc");
String str3 = new String("abc");
記憶體模型:
new String("abc")
每次執行都會創建一個新的String實例,在堆中分配一個新的空間。而使用String str1 = "abc"
每次調用都將指向常量池中同一個常量,不會產生新的實例。
對於可變對象,那些一旦計算出來就不再變化的子對象或常量可單獨提取到靜態域中。
class Person {
private final Date birthDate;
private static final Date BOOM_STATE; //第一次初始化後將不改變,所有對象共用
private static final Date BOOM_END;
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_STATE = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_STATE) >= 0 && birthDate.compareTo(BOOM_END) < 0;
}
}
當心無意識的自動裝箱,要優先使用基本類型而不是包裝類
例如:計算所有int正值的和
Long sum = 0L; //應使用基本類型long
for(long i=0; i<Integer.MAX_VALUE; i++ ) {
sum += i; //自動裝箱,將創造2^32個多餘的Long實例,降低性能
}
System.out.println(sum);
6、消除過期的對象引用
過期引用是指永遠也不會再被解除的引用。在java中記憶體泄漏是隱藏的(無意識的對象保持)。如果一個對象的引用被無意識的保留下來,那麼垃圾回收機制不會回收這個對象及這個對象所持有的所有對象。
消除過期引用最好的方法是讓包含該引用的變數結束其生命周期。只要類自己管理記憶體,就應該小心記憶體泄漏問題。
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 o) {
ensureCapacity();
elements[size++] = o;
}
public Object pop() {
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; //【避免記憶體泄漏】
return result;
}
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2*size + 1);
}
}
}
對於垃圾回收器而言,elements數組中的所有對象的引用都同等有效,它無法區分哪些可以回收哪些不能回收。
記憶體泄漏的常見來源:
數組
緩存。使用WeakHashMap代表緩存,對於複雜的緩存必須使用java.lang.ref
監聽器及其回調。要及時取消註冊,可以使用弱引用
7、避免使用終結方法
終結方法finalizer()為類Object中的方法,當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。
終結方法是不可預測的,通常不要直接調用。
Java語言規範不保證終結方法會被立即執行,所以不應該依賴終結方法來更新重要的持久狀態。
終結方法可用作安全網或終止非關鍵的本地資源(記住調用super.finalize())