程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Effective Java讀書筆記

Effective Java讀書筆記

編輯:JAVA綜合教程

Effective Java讀書筆記


引言

創建和銷毀對象

何時以及如何創建對象,何時以及如何避免創建對象,如何確保創建的對象能夠被適時地銷毀,以及如何管理銷毀之前必須進行的所有清楚工作

第1條 考慮用靜態工廠方法代替構造器

靜態工廠方法與構造器不同的第一大優勢在於:它們有名稱 靜態工廠方法與構造器不同的第二大優勢在於:不必再每次調用它們的時候都創建一個新對象 靜態工廠方法與構造器不同的第三大優勢在於,它們可以返回原返回類型的任何子類型的對象

服務提供者框架包含三個組件:

服務接口 提供者實現
提供者注冊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功能類似,在工廠方法處於不同的類中的時候使用

第2條 遇到多個構造器參數時要考慮用構建器

靜態工廠和構造器的局限:**不能很好地擴展到大量的可選參數**

重載構造器模式可行,但是當有許多參數的時候,客戶端代碼會很難編寫,且難以閱讀

**一種解決方法** 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模式是個不錯的選擇

第3條 用私有構造器或者枚舉類型強化Singleton屬性

使類成為Singleton使客戶端測試十分困難,因為無法替換其模擬實現,除非實現一個充當類型的接口 兩種Singleton方法
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(){ ... }
}

享有特權的客戶端可以借助AccessibleObject.setAccessible方法,通過反射機制調用私有構造器

第三種Singleton實現方法,編寫一個包含單個元素的枚舉類型
public enum Elvis
{
    INSTANCE;
    public void leaveTheBuilding() { ... }
}
更加簡潔,無償地提供了序列化機制,絕對防止多次實例化,**單元素的枚舉類型已經成為實現Singleton的最佳方法**

第4條 通過私有構造器強化不可實例化的能力

企圖通過將類做成抽象類來強制該類不可被實例化,是行不通的 一種私有構造器實現
public class UtilityClass
{
    private UtilityClass()
    {
        throw new AssertionError();
    }
}

第5條 避免創建不必要的對象

一般來說,最好能重用對象而不是在每次需要的時候就創建一個相同功能的新對象
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;
    }
}
**要優先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱** 通過維護自己的對象池來避免創建對象並不是一種好的做法,除非池中的對象是非常重量級的,真正正確使用對象池的典型對象示例就是數據庫連接池 一般而言,維護自己的對象池會增加代碼的復雜性,增加內存占用,還會損害性能

當應該重用現有對象的時候,不要創建新的對象
當該創建新對象的時候,不要重用現有的對象
在提倡使用保護性拷貝的時候,因重用對象而付出的代價要遠遠大於因創建重復對象而付出的代價
必要時如果沒能實施保護性拷貝,將會導致潛在的錯誤和安全漏洞;不必要地創建對象則只會影響程序的風格和性能

第6條 消除過期的對象引用

過期引用,指永遠也不會再被解除的引用。 如果一個對象引用被無意識地保留起來了,那麼垃圾回收機制不僅不會處理這個對象,而且也不會處理被這個對象所引用的所有其他對象

清空對象引用是一種例外,而不是一種規范行為
只要類是自己管理內存,應該警惕內存洩漏問題

內存洩漏的另外一個常見來源是**緩存**:只要在緩存之後存在對某個項的鍵的引用,該項就有意義,可以用`WeakHashMap`代表緩存;當緩存中的項過期之後,會自動被刪除

只有當所要的緩存項的生命周期是由該鍵的外部引用而不是由值決定時,WeakHashMap才有用處

內存洩漏的第三個常見來源是**監聽器和其他回調**:確保回調立即被當做垃圾回收的最佳方法是只保存它們的弱引用

第7條 避免使用終結方法

**終結方法(finalize)通常是不可預測的,也是很危險的,一般情況下是不必要的** 終結方法的缺點在於不能保證會被及時地執行 Java語言規范不僅不保證終結方法會被及時地執行,而且根本就不保證其被執行,**不應該依賴終結方法來更新重要的持久狀態** 使用終結方法有一個非常嚴重的性能損失 對於確實需要終止的方法,應**提供一個顯示的終止方法**,並要求改類的客戶端在每個實例不再有用的時候調用這個方法 **顯式的終止方法通常與try-finally結構結合起來使用,以確保及時終止** 終結方法有兩種合法用途 當對象所有者忘記調用顯式終止方法時,終結方法充當“安全網” 與對象的本地對等體有關。本地對等體是一個本地對象,普通對象通過本地方法委托給一個本地對象,垃圾回收器無法感知本地對象的存在,當Java對等體被回收時,它不會被回收

“終結方法鏈”不會被自動執行,需要進行顯式調用
另外一種可選方法是終結方法守衛者

對於所有對象都通用的方法

對equals、hashCode、toString、clone和finalize深入分析

類和接口

關於類和接口的一些指導原則

泛型

枚舉和注解

方法

如何處理參數和返回值,如何設計方法簽名,如何為方法編寫文檔,在可用性、健壯性和靈活性上有進一步的提升

通用程序設計

討論局部變量的處理、控制結構、類庫的使用、各種數據類型的用法,以及反射機制和本地方法的用法,並討論優化和命名慣例

異常

如何發揮異常的優點,提高程序的可讀性、可靠性和可維護性,以及減少使用不當所帶來的負面影響,並提供了異常使用的指導原則

並發

序列化

闡述序列化方面的技術,討論序列化代理模式

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved