創建和銷毀對象概述
何時以及如何創建對象
何時以及如何避免創建對象
如何確保對象適時地銷毀
如何管理對象銷毀之前必須進行的各種清理動作
一.考慮用靜態工廠方法代替構造器
構造器是創建一個對象實例最基本也最通用的方法,大部分開發者在使用某個 class 的時候,首先需要考慮的就是如何構造和初始化一個對象示例,而構造的方式首先考慮到的就是通過構造函數來完成,因此在看 javadoc 中的文檔時首先關注的函數也是構造器。然而在有些時候構造器并非我們唯一的選擇,我們可以通過靜態類工廠的方式來創建 class 的實例,如
public static Boolean valueOf(boolean b) {
return b?Boolean.TRUE:Boolean.FALSE;
}
相比于構造器,靜態工廠方法的優勢:
1.有意義的名稱
構造方法本身沒有名稱,不能確切的返回描述返回的對象,具有適當名稱的靜態工廠方法更容易被使用,產生的代碼也更容易被閱讀。
2.不必每次調用的時候創建一個新的對象
構造方法的每次調用都會重新創建一個新的實例,而靜態工廠方法可以預先構建好實例/實例緩存,進行重復利用,從而避免不必要的重復對象的創建。
3.能返回原返回類型的任何子類型的對象
構造方法只能返回類本身,而靜態方法可以返回它的子類,利用靜態使得方法更加靈活。
4.創建參數化實例的時候,它們使代碼變得更加簡潔
Map<String,List<String>> stringListMap = new HashMap<>();
由于 Java 在構造函數的調用中無法進行類型的推演,因此也就無法通過構造器的參數類型來實例化指定類型參數的實例化對象。然而通過靜態工廠方法則可以利用參數類型推演的優勢,避免了類型參數在一次聲明中被多次重寫所帶來的煩憂,見如下代碼:
public static <K,V> HashMap<K,V> newInstance() {
return new HashMap<K,V>();
}
?```
//調用
```java
Map<String,List<String>> m = HashMap.newInstance();
二.遇到多個構造器參數時要考慮用構建器
當實例化一個類時,特別是有很多可選的參數,如果我們考慮使用寫很多不同參數的構造方法,就會使得可讀性變得很差。這個時候推薦 Builder 模式來創建這個帶有很多可選參數的實例對象。
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 {
//對象的必選參數
private final int servingSize;
private final int servings;
//對象的可選參數的缺省值初始化
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) {
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();
System.out.println(cocaCola);
}
三.用私有構造器或枚舉類型強化 Singleton 屬性
在 Java1.5 之前,實現 Singleton 由兩種方法。這兩種方法都要把構造器保持為私有的,并導出公有的靜態成員,以便允許客戶端能夠訪問該類的唯一實例。
1.將構造函數私有化,直接通過靜態公有的final域字段獲取單實例對象:
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elivs() { ... }
}
2.通過公有域成員的方式返回單實例對象:
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elivs() { ... }
public static Elvis getInstance() {
return INSTANCE;
}
public void leaveTheBuilding() { ... }
}
Java1.5 起,實現 Singleton 還有第三種方法。只需編寫一個包含單個元素的枚舉類型。單類型的枚舉類型是實現單利模式的最佳方法。
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
四.通過私有構造器強化不可實例化的能力
不可實例化是指當前的類只包含 靜態方法和靜態域的類。在 Java 中,只有當類不包含顯示的構造器時,編譯器才會生成缺省的構造器,因為只要讓類包含私有的構造器,它就不能被實例化。
public class BitmapUtils {
private BitmapUtils() {
throw new AssertionError();
}
}
五.避免創建不必要的對象
String s = new String("stringette");
String s = "stringette";
比較這兩行代碼,上面的語句每次執行的時候都會創建一個新的 String 實例,但是這些創建對象的動作全都是不必要的。而下面的語句只用了一個 String 實例,而不是每次執行的時候都創建一個新的實例。由于 String 被實現為不可變對象,JVM 底層將其實現為常量池,所有值等于"stringette"的對象實例共享同一對象地址,而且還可以保證,對于所有在同一 JVM 中運行的代碼,只要他們包含相同的字符串字面常量,該對象就會被重用。
除了重用不可變的對象之外,也可以重用那些已知不會被修改的可變對象。
public class Person {
private final Date birthDate;
//判斷該嬰兒是否是在生育高峰期出生的。
public boolean isBabyBoomer {
Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
c.set(1946,Calendar.JANUARY,1,0,0,0);
Date dstart = c.getTime();
c.set(1965,Calendar.JANUARY,1,0,0,0);
Date dend = c.getTime();
return birthDate.compareTo(dstart) >= 0 && birthDate.compareTo(dend) < 0;
}
}
public class Person {
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
c.set(1946,Calendar.JANUARY,1,0,0,0);
BOOM_START = c.getTime();
c.set(1965,Calendar.JANUARY,1,0,0,0);
BOOM_END = c.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
}
}
改進后的 Person 類只是在初始化的時候創建 Calender、TimeZone 和 Date 實例一次,而不是在每次調用 isBabyBoomer 方法時都創建一次它們。如果該方法會被頻繁調用,效率的提升將會極為顯著。
六.消除過期對象的引用
內存泄露:如果一個棧先是增長,然后收縮,那么,從棧中彈出來的對象是不會被當做垃圾回收的,即使使用棧的程序不再引用這些對象,它們也不會被回收。這是因為,棧內部維護著對這些對象的過期引用。由于過期引用的存在, GC 并不會去回收它們,所以需要我們手動清空這些引用。比如 :
public Object pop() {
if(size==0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; //Eliminate obsolete reference
return result;
}
七.避免使用終結方法
終結方法的好處
當對象的所有者忘記調用前面段落中建議的顯式終止方法時,終結方法可以充當“安全網”
終止非關鍵的本地資源
終結方法的缺點
終結方法(finalizer)通常是不可預測的,也是很危險的,一般情況下是不必要的。使用終結方法會導致行為不穩定、降低性能,以及可移植性問題。
終結方法不能保證會被及時地執行
不應該依賴終結方法來更新重要的持久狀態
使用終結方法有嚴重的性能損失
總結
以上是我對閱讀了《 Effective Java 》第一篇之后的一些總結,希望可以幫助大家寫出更優雅的代碼。