第四章、類和接口(一)

第十三條、使類和成員的可訪問(wèn)性最小化

  1. 設(shè)計(jì)良好的模塊會(huì)隱藏所有的實(shí)現(xiàn)細(xì)節(jié),把它的API和它的實(shí)現(xiàn)清晰地隔離開來(lái)。然后模塊之間只通過(guò)它們的API進(jìn)行通信,一個(gè)模塊不需要知道其他模塊的內(nèi)部工作情況。(信息隱藏(infomation hiding)和封裝(encapsulation))
    好處:可以有效地解除組成系統(tǒng)的各模塊之間的耦合關(guān)系,使得這些模塊可以獨(dú)立地開發(fā)、測(cè)試、優(yōu)化、使用、理解和修改。
    java的訪問(wèn)機(jī)制決定了類、接口和成員的可訪問(wèn)性。實(shí)體的可訪問(wèn)性是由該實(shí)體聲明所在的位置以及訪問(wèn)修飾符共同決定的。

  2. 對(duì)于頂層(非嵌套)的類和接口,只有兩種可能的訪問(wèn)級(jí)別:包級(jí)私有(package-private)和公有的(public),如果用public修飾符聲明了頂層類或者接口,那他就是公有的,否則它將是包級(jí)私有的。通過(guò)把類做成包級(jí)私有,它實(shí)際上成了這個(gè)包的實(shí)現(xiàn)部分,而不是該包導(dǎo)出API的一部分。如果一個(gè)包級(jí)私有的頂層類或者接口只是在某一個(gè)類的內(nèi)部被用到,就應(yīng)該考慮使它成為唯一使用它的那個(gè)類的私有嵌套類。

  3. 對(duì)于成員(域、方法、嵌套類和嵌套接口)有四種可能的訪問(wèn)級(jí)別:

    • 私有的private:只有在聲明該成員的頂層類內(nèi)部才可以訪問(wèn)這個(gè)成員。
    • 包級(jí)私有的:聲明該成員的包內(nèi)部的任何類都可以訪問(wèn)這個(gè)成員。“缺省default的訪問(wèn)級(jí)別
    • 受保護(hù)的protected:聲明該成員的類的子類可以訪問(wèn)這個(gè)成員,且聲明該成員的包內(nèi)部的任何類也可以訪問(wèn)這個(gè)成員。受保護(hù)的成員是類的導(dǎo)出的API的一部分,必須永遠(yuǎn)得到支持。導(dǎo)出的類的受保護(hù)成員也代表了該類對(duì)于某個(gè)實(shí)現(xiàn)細(xì)節(jié)的公開承諾,應(yīng)該盡量少用。
    • 公有的public:在任何地方都可以訪問(wèn)。
  4. 如果方法覆蓋了超類中的一個(gè)方法,子類中的訪問(wèn)級(jí)別就不允許低于超類中的訪問(wèn)級(jí)別。這樣可以確保任何可使用超類實(shí)例的地方也都可以使用子類的實(shí)例。如果一個(gè)類實(shí)現(xiàn)了一個(gè)接口,那么接口中的所有類方法在這個(gè)類中也都必須被聲明為公有的。(因?yàn)?strong>接口中所有的方法都隱含著公有訪問(wèn)級(jí)別。)

  5. 除了公有靜態(tài)final域的特殊情況之外,公有類都不應(yīng)該包含公有域,并且要確保公有靜態(tài)final域所引用的對(duì)象都是不可變的。


第十四條、在公有類中使用訪問(wèn)方法而非公有域

應(yīng)該用包含私有域和公有訪問(wèn)/設(shè)置方法的類帶進(jìn)行封裝。


第十五條、使可變性最小化

  1. 不可變類只是其實(shí)例不能被修改的類。每個(gè)實(shí)例中包含的信息都必須在創(chuàng)建該實(shí)例的時(shí)候提供,并在對(duì)象的整個(gè)生命周期內(nèi)固定不變。(比如:String、基本類型的包裝類、BigInteger和BigDecimal)
    不可變類的優(yōu)點(diǎn):更加易于設(shè)計(jì)、實(shí)現(xiàn)和使用,不容易出錯(cuò)且更加安全。

  2. 使類成為不可變遵循的五條規(guī)則:

    • 不要提供任何會(huì)修改對(duì)象狀態(tài)的方法(mutator);
    • 保證類不會(huì)被擴(kuò)展。
    • 使所有的域都是final的。
    • 使所有的域都成為私有的。
    • 確保對(duì)于任何可變組件的互斥訪問(wèn)。
  3. 一個(gè)類的實(shí)例:

     /**
      * Created by laneruan on 2017/6/7.
      * 這個(gè)類表示一個(gè)復(fù)數(shù)。
      * 這些算術(shù)運(yùn)算都是創(chuàng)建并返回新的Complex實(shí)例,而不是修改這個(gè)實(shí)例的做法。
      * 這種被稱為函數(shù)的做法,因?yàn)檫@些方法返回惡一個(gè)函數(shù)的結(jié)果,這些函數(shù)對(duì)操作數(shù)進(jìn)行運(yùn)算但不修改它。
      * 對(duì)應(yīng)的是過(guò)程式或命令式的做法。
      */
     public class Complex {
         private final double re;
         private final double im;
     
         //對(duì)于頻繁使用的值,為他們提供公有的的靜態(tài)常量。
         public static final Complex ZERO = new Complex(0,0);
         public static final Complex ONE = new Complex(1,0);
         public static final Complex I = new Complex(0,1);
     
         public Complex(double re,double im){
             this.re = re ;
             this.im = im ;
         }
         //使類變成final的一種方式
     //    private Complex(double re,double im){
     //        this.re = re ;
     //        this.im = im ;
     //    }
         public static Complex valueOf(double re,double im){
             return new Complex(re,im);
         }
     //  Accessors with no corresponding mutators
         //基于極坐標(biāo)創(chuàng)建復(fù)數(shù)
         public static Complex valueOfPolar(double r,double theta){
             return new Complex(r * Math.cos(theta),r * Math.sin(theta));
         }
         public double realPart(){return re;}
         public double imaginaryPart(){return im;}
     
         public Complex add(Complex c){
             return new Complex(re + c.re,im + c.re);
         }
         public Complex subtract(Complex c) {
             return new Complex(re - c.re,im - c.im);
         }
         public Complex multiply(Complex c){
             return new Complex(re*c.re-im*c.im,re*c.im+im*c.re);
         }
         public Complex divide(Complex c){
             double tmp = c.re * c.re + c.im * c.im;
             return new Complex((re*c.re+im*c.im)/tmp,(im*c.re-re*c.im)/tmp);
         }
         @Override
         public boolean equals(Object o) {
             if(o == this){
                 return true;
             }
             if(!(o instanceof Complex)){
                 return false;
             }
             Complex c = (Complex) o ;
             return Double.compare(re,c.re) == 0
                     && Double.compare(im,c.im) == 0;
         }
         @Override
         public int hashCode(){
             int result = 17 + hashDouble(re);
             result = 31 * result + hashDouble(im);
             return result;
         }
         private int hashDouble(double val){
             long longBits = Double.doubleToLongBits(val);
             return (int)(longBits ^ (longBits >>>32));
         }
         @Override
         public String toString(){
             return "(" + re + "+" + im+"i)";
         }
     }
    

    這個(gè)類表示一個(gè)復(fù)數(shù)。這些算術(shù)運(yùn)算都是創(chuàng)建并返回新的Complex實(shí)例,而不是修改這個(gè)實(shí)例的做法。這種被稱為函數(shù)式的做法,因?yàn)檫@些方法返回了一個(gè)函數(shù)的結(jié)果,這些函數(shù)對(duì)操作數(shù)進(jìn)行運(yùn)算但不修改它。對(duì)應(yīng)的是過(guò)程式或命令式的做法。

    這種函數(shù)方法的優(yōu)點(diǎn)帶來(lái)了不可變性,不可變對(duì)象只有一種狀態(tài),即被創(chuàng)建時(shí)的狀態(tài)。不可變對(duì)象本質(zhì)上是線程安全的,不要求同步。當(dāng)多個(gè)線程并發(fā)訪問(wèn)這樣的對(duì)象,不會(huì)發(fā)生破壞,所以不可變對(duì)象可以被自由地共享。不可變對(duì)象為其他對(duì)象提供了大量的構(gòu)件,如果知道一個(gè)復(fù)雜對(duì)象內(nèi)部的組件不會(huì)改變,要維護(hù)它的不變性約束是比較容易的。

    不可變類的真正唯一缺點(diǎn)在于:對(duì)于每個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。

  4. 如何使不可變類自身不被子類化?除了使類成為final外,讓類的所有構(gòu)造器都變成私有的或者包級(jí)私有的,并添加共有的靜態(tài)工廠來(lái)代替公有的構(gòu)造器。以Complex為例:

    //這種方式雖不常用,但是最靈活。而且可以肯定不能擴(kuò)展。
     private Complex(double re,double im){
         this.re = re ;
         this.im = im ;
     }
     public static Complex valueOf(double re,double im){
         return new Complex(re,im);
     }
    
  5. 有關(guān)不可變類的序列化:

實(shí)現(xiàn)Serializable接口,并且它包含一個(gè)或者多個(gè)指向可變對(duì)象的域,就必須提供一個(gè)顯式的readObject或者readResolve方法,或者使用ObjectOutPutStream.writeUnshared和ObjectInputStream.readUnshared方法,即使默認(rèn)的序列化形式是可接受的。


第十六條、復(fù)合優(yōu)先于繼承

  1. 繼承(inheritance)是實(shí)現(xiàn)代碼重用的有力手段,但是使用不當(dāng)會(huì)導(dǎo)致軟件變得很脆弱,在包的內(nèi)部使用繼承是非常安全的,在那里,子類和父類的實(shí)現(xiàn)都處在同一個(gè)程序員的控制下。然而,對(duì)于普通的具體類進(jìn)行跨越包邊界的繼承,則是非常危險(xiǎn)的。繼承打破了封裝性,子類依賴于父類中特定功能的實(shí)現(xiàn)細(xì)節(jié)。父類的實(shí)現(xiàn)有可能會(huì)隨著發(fā)行版本的不同而有所變化,子類可能隨之遭到破壞,因此子類也必須隨著父類的更新而演變。

  2. 導(dǎo)致子類脆弱的一個(gè)相關(guān)原因是:它們的父類在后續(xù)發(fā)行版本中可以獲得新的方法。這些問(wèn)題都來(lái)源于覆蓋(overriding)動(dòng)作。下面是一個(gè)脆弱的實(shí)例:

     import java.util.Arrays;
     import java.util.Collection;
     import java.util.HashSet;
     /**
      * Created by laneruan on 2017/6/7.
      * 需要查詢HashSet。看看自從它從被創(chuàng)建以來(lái)曾經(jīng)添加了多少個(gè)元素。
      * HashSet類中添加元素的方法:add和addAll,因此這兩個(gè)方法都要覆蓋,但并不能正常工作。
      * 因?yàn)樵贖ashSet的內(nèi)部,addAll方法是基于add實(shí)現(xiàn)的,所以通過(guò)addAll方法增加的每個(gè)元素都計(jì)算了兩次。
      * 此時(shí)可以通過(guò)去掉addAll的覆蓋方法來(lái)修正這個(gè)問(wèn)題,但是這是十分脆弱的,它的功能正確性是需要依賴于HashSet的addAll方法是在
      * add方法上實(shí)現(xiàn)的,不能保證隨著發(fā)行版本的不同而不發(fā)生變化。所以此時(shí)的InstrumentedHashSet是十分脆弱的。
      */
     //Broken - Inappropriate use of inheritance
     public class InstrumentedHashSet<E> extends HashSet<E> {
         private int addCount = 0 ;
         public InstrumentedHashSet(){}
         public InstrumentedHashSet(int initCap,Float loadFactor){
             super(initCap,loadFactor);
         }
         @Override
         public boolean add(E e){
             addCount ++;
             return super.add(e);
         }
         @Override
         public boolean addAll(Collection<? extends E> c){
             addCount += c.size();
             return super.addAll(c);
         }
         public int getAddCount(){
             return addCount;
         }
         public static void main(String[] args){
             InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
             s.addAll(Arrays.asList("Snap","Pop","Crackle"));
             System.out.println(s.getAddCount());//打印出來(lái)是6?
         }
     }
    
  3. 復(fù)合(composition):不用擴(kuò)展現(xiàn)有的類,而是在新的類中增加一個(gè)私有域,它引用現(xiàn)有類的一個(gè)實(shí)例。因?yàn)楝F(xiàn)有的類變成了新類的一個(gè)組件。新類中的每個(gè)實(shí)例方法都可以調(diào)用被包含的現(xiàn)有類實(shí)例中對(duì)應(yīng)的方法,并返回它的結(jié)果。這被稱為轉(zhuǎn)發(fā)(forwarding),新類中的方法被稱為轉(zhuǎn)發(fā)方法。這樣新得到的類會(huì)非常穩(wěn)固,不依賴于現(xiàn)有類的實(shí)現(xiàn)細(xì)節(jié)。下面是上面脆弱的實(shí)例的復(fù)合版本:

     import java.util.*;
     
     /**
      * Created by laneruan on 2017/6/8.
      * Set接口的存在使得InstrumentedSet類的設(shè)計(jì)成為可能,因?yàn)镾et類保存了HashSet類的功能特性。
      * 從本質(zhì)上來(lái)說(shuō)這個(gè)類把一個(gè)Set變成了一個(gè)增加計(jì)數(shù)功能的Set。
      * 因?yàn)槊總€(gè)InstrumentedSet實(shí)例都把另一個(gè)Set實(shí)例包裝起來(lái)了,所以稱為wrapper class
      */
     //Wrapper class 包裝類
     public class InstrumentedSet<E> extends ForwardingSet<E>{
         private int addCount = 0;
         public InstrumentedSet(Set<E> s){
             super(s);
         }
         @Override
         public boolean add(E e){
             addCount++;
             return super.add(e);
         }
         @Override
         public boolean addAll(Collection<? extends E> c){
             addCount += c.size();
             return super.addAll(c);
         }
         public int getAddCount(){
             return addCount;
         }
         public static void main(String[] args){
             InstrumentedSet<String> s = new InstrumentedSet<String>(new HashSet<String>());
             s.addAll(Arrays.asList("Snap","Pop","Crackle"));
             System.out.println(s.getAddCount());//打印出來(lái)是3
             System.out.println(s);
         }
     }
     //Reusable forwarding class
     class ForwardingSet<E> implements Set<E>{
         private final Set<E> s;
         public ForwardingSet(Set<E> s){this.s = s;}
         @Override
         public int size() {
             return s.size();
         }
         @Override
         public boolean isEmpty() {
             return s.isEmpty();
         }
         @Override
         public boolean contains(Object o) {
             return s.contains(o);
         }
         @Override
         public Iterator<E> iterator() {
             return s.iterator();
         }
         @Override
         public Object[] toArray() {
             return s.toArray();
         }
         @Override
         public <T> T[] toArray(T[] a) {
             return s.toArray(a);
         }
         @Override
         public boolean add(E e) {
             return s.add(e);
         }
         @Override
         public boolean remove(Object o) {
             return s.remove(o);
         }
         @Override
         public boolean containsAll(Collection<?> c) {
             return s.containsAll(c);
         }
         @Override
         public boolean addAll(Collection<? extends E> c) {
             return s.addAll(c);
         }
         @Override
         public boolean retainAll(Collection<?> c) {
             return s.retainAll(c);
         }
         @Override
         public boolean removeAll(Collection<?> c) {
             return s.removeAll(c);
         }
         @Override
         public void clear() {
             s.clear();
         }
         @Override
         public boolean equals(Object o) {
             return s.equals(o);
         }
         @Override
         public int hashCode() {
             return s.hashCode();
         }
         @Override
         public String toString() {
             return s.toString();
         }
     }
    
  4. 什么時(shí)候使用繼承?

只有當(dāng)子類真正是父類的子類型(subtype)時(shí),即當(dāng)兩者之間確實(shí)存在“is-a”關(guān)系時(shí),類B才應(yīng)該擴(kuò)展A。如果不能確定每個(gè)B確實(shí)都是A,通常情況下,B應(yīng)該包含A的一個(gè)私有實(shí)例,而且暴露一個(gè)較小的、較簡(jiǎn)單的API: A本質(zhì)上不是B的一部分,只是它的實(shí)現(xiàn)細(xì)節(jié)而已。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問(wèn)題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,921評(píng)論 0 33
  • GeekBand C++ Week1 Notes A.OOP-面向?qū)ο缶幊?1基礎(chǔ):C語(yǔ)言 -變量variable...
    古來(lái)征戰(zhàn)幾人回閱讀 598評(píng)論 0 0
  • 我們終究會(huì)隨風(fēng)而去,
    無(wú)為七七閱讀 217評(píng)論 0 0
  • 四年的時(shí)間飛快地過(guò)去了?;叵脒@四年間你所克服的挑戰(zhàn),你在學(xué)業(yè)上的收獲,你的性格的養(yǎng)成,由衷地祝賀兒子! 繼續(xù)學(xué)業(yè),...
    喜樂人生之維也納閱讀 332評(píng)論 0 1

友情鏈接更多精彩內(nèi)容