effective java 讀書筆記——類和介面

來源:互聯網
上載者:User

標籤:

上周因為準備考試等一堆原因,沒空看書,今天補上一點。

 

類和介面是java程式設計語言的核心,它們也是java語言的基本抽象單元,java語言提供了很多強大的基本元素,供程式員設計類和介面,這一章講的是一些指導原則,可以設計出更加有用,健壯和靈活的類和介面。

第1條:使類和成員的可訪問性最小化

首先說一個概念:模組之間只能通過它們的API進行通訊,一個模組不需要知道其他模組的內部工作情況,這個概念叫做“資訊隱藏”,或者“封裝”。(對,這就是物件導向的中封裝繼承多態三大特性之一的封裝)

資訊隱藏之所以重要,是因為:它可以有效解除系統各模組之間的耦合關係,使得這些模組可以獨立的開發,測試,最佳化,使用,理解和修改。這樣可以加快系統開發的速度,同時也減輕了維護的負擔。

Java語言提供了許多機制來協助資訊隱藏。存取控制機制決定了類,介面和成員的可訪問性。實體的可訪問性是由該實體聲明所在的位置,以及該實體聲明中所出現的存取修飾詞(private,protected,public)共同決定的。第一規則是:儘可能使每個類或者成員不被外界訪問。

對於成員,有4種存取層級,按照可訪問性遞增順序排列:

private: 只在聲明該成員的頂層 類內部才可以訪問。

package private: 聲明該成員的包內部的任何類都可以訪問該成員,是預設的存取層級(即如果沒指定存取層級,就預設是package private的。)

protected:聲明該成員的類的子類可以訪問該成員,並且,聲明該成員的包內部的任何類都可以訪問。

public:在任何地方都可以訪問該成員。

在判定一個成員的存取層級時,不僅要考慮我們想要這個成員暴露的程度,還有一些注意事項:

1.執行個體域絕對不能是public的。一旦使執行個體域成為共有,就放棄了讀儲存在這個域中的值進行限制的能力。或者說,對於一個執行個體域來說,它自己內部的屬性應該由它自己掌控,但是如果它是Public的,那麼所有人都可以掌控它的內部的值,可以拿兩個國家來想象,如果一個國家內部的事務被另一個國家插手了,這感覺好喪權辱國。。。所以,對象也是有自尊的!絕對不能把它聲明成Public。

2.對於final域,如果final修飾的是常量的話,可以通過public static final 來暴露這些常量。但如果final修飾的是引用的話,就不能用Public了,因為final的本意是不能修改,雖然引用的本身不會改變,但是它指向的對象卻可能發生變化,這就違背了final的本意。

3.注意,長度非0的數組總是可變的,所以,類具有public static final的數組域,或是返回這種數組域的存取方法,這總是錯誤的。因為這樣用戶端能夠修改數組中的內容,這是安全性漏洞的一個常見原因。例如:

public static fnal Thing[] VALUES=  {...};   //這樣是錯的

可以這樣改進:

private static final Thing[] PRIVATE_VALUES={...};public static final List<Thing> VALUES =      Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

或者這樣:

private static final Thing[] PRIVATE_VALUES={...};public static final Thing[] values(){     return PRIVATE_VALUES.clone();}
第2條:在共有類中使用存取方法而非共有域

這一條很簡單,就是盡量使用getter和setter方法來訪問共有類中的資料,而不是讓資料自己public。例子:

class Point{  //這是不好的寫法    public double x;    public double y;}
class Point{//這是較好的寫法,使用存取方法訪問共有域    private double x;    private double y;    public Point(double x,double y){        this.x = x;        this.y = y;    }    public double getX(){ return x;}    public double getY(){  return y;}    public void setX(double x){this.x = x;}    public void setY(double y){ this.y = y;}    }
第3條:使可變性最小化

首先介紹不可變類:不可變類是其執行個體不能被修改的類。每個執行個體中包含的所有資訊都必須在建立該執行個體的時候就提供,並在對象的整個生命週期內固定不變。Java中有許多不可變的類,比如String,BigInteger,BigDecimal。不可變類比可變類更易於設計,實現和使用,它們不容易出錯,而且更加安全。

設計不可變類有下面5個原則:

1.不會提供任何會修改對象狀態的方法。

2.保證類不會被擴充。(可以聲明為final)

3.使所有域都是final的。

4.使所有的域都是私人的。這樣可以防止用戶端獲得被訪問域的引用的可變對象的許可權,並防止用戶端直接修改這些對象。

5.確保對於任何可變組件的互斥訪問。如果類具有指向可變對象的域,則必須確保該類的用戶端無法獲得指向這些對象的引用,並且,永遠不要用用戶端提供的對象引用來初始化這樣的域,也不要從任何存取方法中返回該對象引用。(比如你家養了條狗,你肯定不會希望別人可以隨隨便便就把它牽走吧,或者是你出門上班,走的時候家裡是條薩摩耶,回來時候變成哈士奇了,請別哭暈在廁所……)

這裡有一個不可變類的例子,複數,但是太長了我懶得打……好吧,來個一小部分

public final class Complex{    private final double re;    private final double im;    public Complex(double re,double im){...}    public double realPart(){return re;}    public double imaginaryPart(){return im;}//這裡是第一條,不會返回修改的方法    public Complex add(Complex c){        return new Complex(re+c.re,im+c.im);    }    public Complex subtract(Complex c){            return new Complex(re-c.re,im-c.im);    }    ...    @Override public boolean equals(Object o){...}    @Override public int hashCode(){...}    @Override public String toString(){...}

那麼,這個類就在這裡啦,注意看這裡的基本算數運算:加減乘除。它們建立並返回新的執行個體,而不是修改當前執行個體,這種方法被稱作functional方法,因為這些方法返回一個函數的結果,這些函數對運算元進行運算但並不修改它。與之對應的是“過程的procedural”方法,會導致運算元的狀態發生改變。

不可變對象本質上是安全執行緒的,他們不要求同步,所以,不可變對象可以被自由的共用。不僅尅共用不可變對象,甚至也可以共用它們的內部資訊。

不可變類真正唯一的缺點是,對於每個不同的值都需要一個單獨的對象。建立這種對象的代價可能很高。如果你執行一個需要很多步驟的操作,每個步驟會產生一個新的對象,但是我們只用最後的結果,其他對象最終都會被丟棄,這時會產生效能問題。處理這個問題有2種方法:1.猜測常用的操作,把它們作為i基本類型提供。2.提供一個共有的可變更配置套類。比如String和StringBuilder。

 

如果類不能被做成是不可變的,仍然應該儘可能的限制它的可變性。因此,除非有令人信服的理由要讓域變成非final ,否則要讓每個域都是final的。

第4條:複合優先於繼承

與方法調用不同的是,繼承打破了封裝性,換句話說,子類依賴於父類中某些功能的實現細節。父類的實現可能因為版本的變化有所變化,那麼子類的功能可能會被破壞,即使它的代碼並沒有改變。

複合是不擴充現有的類,而是在新的類中增加一個私人域,它引用現有類的一個執行個體。現有的類變成了新類的一個組件。

複合中,新類中的每個執行個體方法都可以調用被包含的現有類執行個體中對應的方法,並返回它的結果,這被稱為“轉寄”。比如

public class ForwardingSet<E> implements Set<E>{    private final Set<E> s;    public forwardingSet(Set<E> s){this.s = s;}    public void clear(){s.clear;}//轉寄    public boolean contains(Object o){return s.contans(o);}//轉寄    public boolean isEmpty(){return s.isEmpty();}//轉寄    ...}

繼承可能導致的問題:1.子類中的函數調用了父類的super.f(),而父類的f()中其實又調用了父類的f2()函數,這種“自用性”是實現細節,不是承諾,不能保證Java平台的所有實現都不變,可能會因為版本不同而發生改變。

2.如果在子類中添加了一個父類中沒有的類,但是不巧,在下一版本中父類也有了同名的類,那麼可能會變成一個覆蓋方法,或重載方法,又或者,子類中的這個方法無法遵守父類中方法的約定。

如果在適合用複合的地方使用了繼承,則會暴露實現細節,這樣得到的API會把你限制在原始的實現上,永遠限定了類的效能。更嚴重的是,由於暴露了內部細節,用戶端有可能就直接存取這些細節,會導致語義上的混淆,甚至直接修改超類,從而破壞子類的約束條件。

在決定使用繼承而不是符合之前,還應問自己最後一組問題:對於你試圖擴充的類,它的AI中有沒有缺陷?如果有,你是否願意把那些缺陷傳播到類的API中?

總之,只有確實是is a 關係時,才應該使用繼承。

第5條:要麼為繼承而設計,並提供文檔說明,要麼就禁止繼承

對於為了繼承設計的類,文檔必須全面:

1.文檔必須精確描述覆蓋每個方法所帶來的影響。對於每個公有的或者受保護的方法或構造器,文檔必須指明該方法調用了哪些可覆蓋的方法,是以什麼順序調用的,每個調用的結果又是如何影響後續的處理過程的。

2.必須在文檔中說明,哪些情況下它會調用可覆蓋的方法。

對於為了繼承而設計的類,唯一的測試方法就是編寫子類。

第6條:介面優於抽象類別

介面與抽象類別的區別在於,抽象類別允許包含某些方法的實現,但是介面不允許。

1.現有的類可以很容易被更新,以實現新的金額口。

2.介面是定義混合類型的理想選呢。

3.介面允許我們構造非階層的類型架構。

通過對你匯出的每個重要介面都提供一個抽象的骨架實作類別,可以把介面和抽象類別的優點結合起來。編寫骨架實作類別必須認真研究介面,確定哪些方法是最基本的,其他的方法可以根據它們來實現,這些基本方法將成為骨架實作類別中的抽象方法。然後,為介面中的其他方法提供具體的實現。

第7條:介面只用於定義類型

 當類實現介面時,介面就充當可以引用這個類的執行個體的類型。因此,類實現了介面,就表明用戶端可以對這個類的執行個體實施某些動作。為了其他任何目的而定義介面是不恰當的。

之所以說介面應該只用於定義類型,是因為Java中有一種對介面的不良使用:常量介面。這是反面的典型,比如java.io.ObjectStreamConstants。不值得消防。

effective java 讀書筆記——類和介面

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.