何時以及如何創建對象,何時以及如何避免創建對象,如何確保創建的對象能夠被適時地銷毀,以及如何管理銷毀之前必須進行的所有清楚工作
服務提供者框架包含三個組件:
服務接口 提供者實現
提供者注冊API 系統用來注冊實現,讓客戶端訪問
服務訪問API 客戶端用來獲取服務的實例
服務提供者接口(可選) 負責創建其服務實現的實例
示例代碼
public interface Service
{
public void service();
}
public interface Provider
{
Service newService();
}
public class Services
{
private Services(){}
private static final Map< String, Provider > = new ConcurrentHashMap< String, Provider >();
public static final String DEFAULT_PROVIFER_NAME = "";
public static void registerDefaultProvider( Provider p )
{
registerProvider( DEFAULT_PROVIDER_NAME, p );
}
public static void registerProvider( String name, Provider p )
{
providers.put( name, p );
}
public static Service newInstance()
{
return newInstance( DEFAULT_PROVIDER_NAME );
}
public static Service newInstance( String name )
{
Provider p = providers.get( name );
if ( p == null )
{
throw new IllegalArgumentException( "No provider registered with name : " + name );
}
return p.newServices();
}
}
靜態工廠方法的第四大優勢在於,在創建參數化類型實例的時候,它們使代碼變得更加簡潔
public static< K, V > HashMap< K, V > newInstance()
{
return new HashMap< K, V >();
}
Map< String, List< String > > map = HashMap.newInstance();
valueOf 返回的實例和其參數具有相同的值,實際上是類型轉換方法
of valueOf的替代
getInstance 根據方法的參數返回相應的實例,對於Singleton,無參數,返回唯一的實例
newInstance 和getInstance功能類似,但確保返回的每個實例是新創建的
getType 和getInstance功能類似,在工廠方法處於不同的類中的時候使用,Type表示工廠方法所返回的對象類型
newType 和newInstance功能類似,在工廠方法處於不同的類中的時候使用
**一種解決方法** JavaBean模式 調用一個無參構造器來創建對象,然後調用setter方法來設置每個必要的參數,以及每個相關的可選參數 潛在問題:狀態不一致、阻止了把類做成不可變的可能、 **Builder模式** 安全性和可讀性的折衷重載構造器模式可行,但是當有許多參數的時候,客戶端代碼會很難編寫,且難以閱讀
示例代碼不直接生成想要的對象,讓客戶端利用所有必要的參數調用構造器,得到一個builder對象,然後客戶端在builder對象上調用類似於setter方法,來設置每個相關的可選參數,最後客戶端調用無參的build方法來生成不可變的對象
public class NutritionFacts
{
private final int seringSize;
private final int servings;
private final int calories;
public static class Builder
{
private final int servingSize;
private final int servings;
private final int calories = 0;
public Builder( int servingSize, int servings )
{
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories( int val )
{
calories = val;
return this;
}
public NutritionFacts build()
{
return new NutritionFacts( this );
}
}
private NutritionFacts( Builder builder )
{
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
}
}
//call code
NutritionFacts cocaCola = new NutritionFacts.Builder( 240, 8 ).calories( 100 ).build();
**builder模式模擬了具名的可選參數** Java中傳統的抽象工廠實現是Class對象,用newInstance方法充當builer方法一部分。但newInstance方法總是企圖調用類的無參構造器,而構造器可能不存在,同時,該方法還會傳播由無參構造器拋出的異常,**`Class.newInstance`破壞了編譯時的異常檢查** builer不足:為了創建對象,必須先創建其構建器,可能造成性能問題;比重疊構造器模式更加冗長,只有在很多參數的時候才使用。
如果類的構造器或者靜態工廠中具有多個參數,設計時,Builder模式是個不錯的選擇
public class Elvis
{
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}
//another
public class Elvis
{
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding(){ ... }
}
第三種Singleton實現方法,編寫一個包含單個元素的枚舉類型享有特權的客戶端可以借助
AccessibleObject.setAccessible
方法,通過反射機制調用私有構造器
public enum Elvis
{
INSTANCE;
public void leaveTheBuilding() { ... }
}
更加簡潔,無償地提供了序列化機制,絕對防止多次實例化,**單元素的枚舉類型已經成為實現Singleton的最佳方法**
public class UtilityClass
{
private UtilityClass()
{
throw new AssertionError();
}
}
String s = new String( "stringtest" );//Donot do this
String s = "stringtest";
對於同時提供了靜態工廠方法和構造器的不可變類,通常優先使用靜態工廠方法,避免創建不必要的對象
public class Person
{
private final Date birthDate;
//Donot do this
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
&& birthData.compareTo( boomEnd )<0;
}
}
//better implements
public class Person
{
private final Date 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 );
BOOM_START = gmtCal.getTime();
gmtCal.set( 1965, Calendar.JANUARY, 1, 0, 0, 0 );
BOOM_END = gmtCal.getTime();
}
//Do this
public boolean isBabyBoomer()
{
return birthDate.compareTo( BOOM_START)>=0
&& birthData.compareTo( BOOM_END)<0;
}
}
**要優先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱** 通過維護自己的對象池來避免創建對象並不是一種好的做法,除非池中的對象是非常重量級的,真正正確使用對象池的典型對象示例就是數據庫連接池 一般而言,維護自己的對象池會增加代碼的復雜性,增加內存占用,還會損害性能
當應該重用現有對象的時候,不要創建新的對象
當該創建新對象的時候,不要重用現有的對象
在提倡使用保護性拷貝的時候,因重用對象而付出的代價要遠遠大於因創建重復對象而付出的代價
必要時如果沒能實施保護性拷貝,將會導致潛在的錯誤和安全漏洞;不必要地創建對象則只會影響程序的風格和性能
內存洩漏的另外一個常見來源是**緩存**:只要在緩存之後存在對某個項的鍵的引用,該項就有意義,可以用`WeakHashMap`代表緩存;當緩存中的項過期之後,會自動被刪除清空對象引用是一種例外,而不是一種規范行為
只要類是自己管理內存,應該警惕內存洩漏問題
內存洩漏的第三個常見來源是**監聽器和其他回調**:確保回調立即被當做垃圾回收的最佳方法是只保存它們的弱引用只有當所要的緩存項的生命周期是由該鍵的外部引用而不是由值決定時,
WeakHashMap
才有用處
“終結方法鏈”不會被自動執行,需要進行顯式調用
另外一種可選方法是終結方法守衛者
對equals、hashCode、toString、clone和finalize深入分析
關於類和接口的一些指導原則
如何處理參數和返回值,如何設計方法簽名,如何為方法編寫文檔,在可用性、健壯性和靈活性上有進一步的提升
討論局部變量的處理、控制結構、類庫的使用、各種數據類型的用法,以及反射機制和本地方法的用法,並討論優化和命名慣例
如何發揮異常的優點,提高程序的可讀性、可靠性和可維護性,以及減少使用不當所帶來的負面影響,並提供了異常使用的指導原則
闡述序列化方面的技術,討論序列化代理模式