核心內容
創建、銷毀物件主要包含以下4個方面的內容:
- 何時以及如何創建物件
- 何時以及如何避免創建物件
- 如何卻奧他們能夠適時地銷毀
- 如何管理物件銷毀之前必須進行的各種清理動作
解決方案及編碼原則
第1條:使用 靜態工廠方法 代替 構造器
類提供一個公有的 靜態工廠方法,例如我們看下 Boolean類的示例
public static Boolean valueOf(boolean b) {
return b ? TRUE : FALSE;
}
注:靜態工廠方法 不等價于 設計模式中的工廠模式
優勢:
- 第一大優勢:相比于構造器,他們有方法名,可以更加準確清晰的描述回傳的物件,可讀性更高,構造器的引數必須不同,限制更嚴格,所以此方式更靈活
- 第二大優勢:相比于構造器,不必在每次呼叫他們的時候都創建一個新物件,靜態方法回傳的物件,內部實作可以快取起來,比如:Boolean.valueOf(boolean)
- 第三大優勢:相比于構造器,可以回傳型別的任何子型別物件,通過方法,回傳值可以設定為 介面 型別,回傳其所有子型別
靈活的靜態工廠方法構成了 服務提供者框架(Service Provider Framework),例如:JDBA(java資料庫連接,Java Database Connectivity)API.
服務提供者框架:多個服務提供者實作一個服務,系統為服務提供者的客戶端提供多個實作,并把他們從多個實作中解耦出來,
包含三個重要的組件(1、2、3)和一個可選組件(4):
- 服務介面(Service Interface):有提供者實作的
- 提供者注冊API(Provider Registration API):系統用來注冊實作,讓客戶端訪問他們的
- 服務訪問API(Service Access API):客戶端用來獲取服務實體的,
- 服務提供者介面(Service Provider Interface):如果沒有介面,那么實作就需要按照類名稱注冊,然后通過反射方式進行實體化,
例如:對于JDBC來說:
- Connection是它的服務介面
- DriverManager.registerDriver是提供者注冊API,
- DriverManager.getConnection是服務訪問API,
- Driver是服務提供者介面
對于 服務訪問API 可以利用配接器(Adapter)模式,回傳比提供者需要的更豐富的服務介面,給一個具體的例子:
//Service Interface
public interface Service{
...//方法定義
}
//Service Provider Interface
public interface Provider{
Service newService();
}
//Noninstantiable class for service registration and access
public class Services{
//阻止實體化
private Services(){}
//創建服務介面的映射關系
private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>";
//Provider registration API ,提供者注冊API
public static void registerDefaultProvder(Provider p){
registerProvider(DEFAULT_PROVIDER_NAME, p);
}
public static void registerProvider(String name, Provder p){
providers.put(name, p);
}
//Service access API
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 provder registered with name:" + name);
}
reutrn p.newService();
}
}
缺點:
- 類如果不含有工友或者受保護的構造器,就不能被子類化
- 它們于其他的靜態方法沒有任何區別,辨識度不高
靜態工廠方法的一些慣用名稱:
- valueOf
- of
- getInstance
- newInstance
- getType
- newType
第2條:遇到多個構造器引數時需要考慮用 構建器(Builder)
多個引數,大家一般會想到兩種方式:
- 多個構造器組成,維護成本高
- 使用 pojo,先new物件,再set,資料可能出于不一致的狀態,執行緒不安全
在這種時候,我們盡量使用 Builder 模式,可以達到以上兩種的優點,示例代碼如下:
//Bulder模式
public class Test {
//必選引數
private int test1;
private int test2;
//可選引數
private int test3;
private int test4;
public static class Builder{
private int test1;
private int test2;
private int test3 = 0;
private int test4 = 0;
public Builder(int test1, int test2){
this.test1 = test1;
this.test2 = test2;
}
public Builder test3(int test3){
this.test3 = test3;
return this;
}
public Builder test4(int test4){
this.test4 = test4;
return this;
}
public Test build(){
return new Test(this);
}
}
public Test(Builder builder) {
this.test1 = builder.test1;
this.test2 = builder.test2;
this.test3 = builder.test3;
this.test4 = builder.test4;
}
public static void main(String[] args) {
Test test = new Test.Builder(1, 2)
.test3(3).test4(4)
.build();
}
}
當大多數引數都是可選的時候,使用Bulder模式,更易于閱讀和撰寫,同時也比JavaBean更加安全,
第3條:用私有構造器*或者列舉型別強化 Singleton 屬性
Singleton 指僅被實體化一次的類,
第一種方式:構造器保持私有,到處公有的靜態成員,
public class TestSingleton {
public static final TestSingleton INSTANCE = new TestSingleton();
private TestSingleton(){
///...
}
}
上邊這種方式,可以通過反射來獲取,可以在構造器中進行判斷,在創建實體時拋出例外,
public class TestSingleton {
public static final TestSingleton INSTANCE = new TestSingleton();
private TestSingleton(){
throw new AssertionError();
}
}
為了用一種更優化的方式,解決上邊的問題,就有了下邊第二種方式
第二種方式:提供 靜態工廠方法 到處共享實體
public class TestSingleton {
private static final TestSingleton INSTANCE = new TestSingleton();
private TestSingleton(){
///...
}
public static TestSingleton getInstance(){
return INSTANCE;
}
}
如果上邊的類,要變成可序列化的
- 僅僅宣告 "implement Serializable" 是不夠的,
- 宣告所有實體域都是瞬時(transient)的
- 同時提供一個 readResolve方法,
否則,每次反序列化一個序列化的實體時,都會創建一個新的實體,
在java 1.5發行版本起,可以使用列舉的方式來實作:
public enum EnumSingleton {
INSTANCE;
}
效果一樣的前提下,代碼更簡潔、并且無償提供了序列化機制,達到絕對防止多次實體化,
第4條:避免創建不必要的物件
通過前邊提到的 3 種方法,去實作避免創建不必要的物件,會極大的提升性能,
延遲初始化是不建議的方式,復雜度增加
物件池的方式也不是一種好的方式,除非是物件是非常重量級,比如:資料庫連接池,
注:因重用物件而付出的代價要遠遠大于因創建重復物件而付出的代價,
第5條:消除過期的物件參考
public class TestStack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INTIAL_CAPACITY = 16;
public TestStack() {
elements = new Object[DEFAULT_INTIAL_CAPACITY];
}
public void push(Object element) {
ensureCapaticy();
elements[size++] = element;
}
public Object pop(){
if (size == 0){
throw new EmptyStackException();
}
return elements[--size];
}
private void ensureCapaticy() {
if (elements.length == size){
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
在上邊的代碼中,pop方法里邊只是回傳了陣列中的資料,這些資料即使沒人用了,也不會被回收,從而導致了記憶體泄漏,這類記憶體泄漏
通常是"無意識的物件保持",
如何解決這類問題,只需要在參考已經過期后,只需要清空這些參考即可,對于pop的微調一下:
public Object pop(){
if (size == 0){
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
針對上邊的改造方式,會讓大家以后過于謹慎,其實沒必要,本身這種方式也是一種例外,不是一種規范行為,
最好的方法是:讓包含該參考的變數結束其生命周期,
通過上邊的例子可以看出:只要類是自己管理記憶體,大家就應該警惕記憶體泄漏問題,一旦元素被釋放掉,就應該將其清空,
記憶體泄漏另一個常見來源是快取,為了保證垃圾正常被回收,使用 WeakHashMap來保存其參考,
還有另外一個常見來源監聽器和回呼,最佳方法同上,只保存他們的弱參考(weak reference),
第6條:避免使用終結(finalizer)方法
終結方法(finalizer)方法通常是不可預測的,也比較危險,會導致行為不穩定,性能下降,可移植性差,
缺點:
- 不能保證會被及時的執行,比如:用終結方法來關閉已經打開的檔案,是嚴重的錯誤用法,而是try finally替代
- 同時也不能保證它們會被執行
注意:不應該依賴終結(finalizer)方法來更新重要的持久狀態, - 有非常嚴重的性能損失
如果類的物件中封裝的資源(例如:檔案、執行緒)確實需要終止,只需提供一個顯示的終止方法, - 顯示的終止方法有以下要求:
1、每個實體不再有用的時候呼叫
2、該實體必須在自己的私有域,記錄下自己是否已經被終止,以便其他方法在呼叫前拋出 IllegalStateException 例外
典型的例子:InputStream、OutputStream、java.util.Connection上的 close 方法,
通常情況下,顯示的終止方法 與 try-finally結構 結合起來使用,以確保及時終止,在 finally 子句內部呼叫顯示的終止方法,保證在例外情況下,也會被執行,
Foo foo = new Foo(...);
try{
...
} finally{
foo.terminate();
}
帶來的好處:
- 充當"安全網",任何情況下都會被執行
- 與物件的本地對等體(natice peer)有關,
本地對等體:一個本地物件(native object),普通物件通過本地方法(native method),委托給一個本地物件,所以,當 它的java對等體被回收時,它(本地物件)不會被回收,這種情況下,就可以使用終止方法來執行回收,
還有一個需要注意的點是“終結方法鏈”,它并不會被自動執行,如果類有終結方法,并且子類覆寫了終結方法,子類的終結方法必須手動呼叫超累的終結方法,
在子類呼叫的代碼中,也還是應該結合 try-finally 結構來呼叫,以保證父類的終結方法被正常呼叫,
最后如果需要把終結方法和公有非final類關聯起來,請考慮使用 結方法守衛者,以確保即使子類的終結方法未能呼叫super.finalizer,該終結方法也被執行,
Read the fucking manual and source code轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/463484.html
標籤:其他
