Effective java筆記(三),類(lèi)與接口

類(lèi)與接口是Java語(yǔ)言的核心,設(shè)計(jì)出更加有用、健壯和靈活的類(lèi)與接口很重要。

13、使類(lèi)和成員的可訪問(wèn)性最小化

設(shè)計(jì)良好的模塊會(huì)隱藏起所有的實(shí)現(xiàn)細(xì)節(jié),僅使用API與其他模塊進(jìn)行通信。這個(gè)概念稱(chēng)為信息隱藏或封裝,是軟件設(shè)計(jì)的基本原則之一。信息隱藏可以是實(shí)現(xiàn)系統(tǒng)各模塊的解耦,以使這些模塊可以獨(dú)立的開(kāi)發(fā)、測(cè)試、優(yōu)化。信息隱藏還提高了軟件的可重用性,降低了構(gòu)建大型系統(tǒng)的風(fēng)險(xiǎn)。

java中實(shí)體的可訪問(wèn)性由實(shí)體聲明的位置以及訪問(wèn)修飾符(private、不寫(xiě)、protected、public)共同決定。盡可能的降低每個(gè)類(lèi)及成員的訪問(wèn)級(jí)別。由于共有類(lèi)是包的導(dǎo)出API的一部分,可以被客戶(hù)端程序直接調(diào)用,對(duì)這種類(lèi)的修改可能會(huì)影響程序向前的兼容性。而包級(jí)私有的類(lèi)(默認(rèn)訪問(wèn)級(jí)別)是包實(shí)現(xiàn)的一部分,不會(huì)對(duì)外提供接口,在以后的發(fā)現(xiàn)版本中可以對(duì)其放心修改。同樣對(duì)于公有類(lèi),其public或protected成員是類(lèi)的導(dǎo)出API的一部分,必須永遠(yuǎn)得到支持

  • 若子類(lèi)覆蓋了超類(lèi)中的方法,則子類(lèi)中的訪問(wèn)級(jí)別不能比超類(lèi)低。否則使用多態(tài)時(shí)會(huì)報(bào)錯(cuò)。

  • 若類(lèi)實(shí)現(xiàn)了一個(gè)接口,則接口中的所有方法在子類(lèi)中都必須是公有的。接口中所有方法隱藏含有public的訪問(wèn)級(jí)別

final域指向可變對(duì)象:需要指出的是final的是引用不是對(duì)象本身。雖然引用本身不能被修改,但它指向的對(duì)象可以被修改。對(duì)于私有的final域,其對(duì)象不能被外部類(lèi)訪問(wèn)和修改從而保證了這個(gè)對(duì)象不可變的能力。而public final域沒(méi)有這個(gè)能力。所以包含公有域的類(lèi)不是線程安全的。對(duì)于不可變對(duì)象,其是線程安全的,無(wú)需擔(dān)心線程間對(duì)象不一致。

長(zhǎng)度非零的數(shù)組總是可變的(例如,將元素null),所以,若類(lèi)具有公有的靜態(tài)final數(shù)組域,或返回這種域的方法,則客戶(hù)端將能夠修改其中的內(nèi)容。這是一個(gè)安全漏洞。

public static final Thing[] VALUES = { ... };

修改方法:


//公有數(shù)組變私有
private static final Thing[] PRIVATE_VALUES = { ... };

//方法1,增加一個(gè)公有的不可變列表
public static final List<Thing> VALUES = 
    Collection.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

//方法2,返回私有數(shù)組的拷貝
public static final Thing[] values() {
    return PRIVATE_VALUES.clone; //注意要完全拷貝
}

確保公有靜態(tài)final域所引用的對(duì)象都是不可變的

14、公有類(lèi)中使用訪問(wèn)方法而非公有域

如果類(lèi)可以在包外對(duì)其進(jìn)行訪問(wèn),應(yīng)該提供訪問(wèn)方法(getter或setter方法),以保留將來(lái)改變?cè)擃?lèi)內(nèi)部實(shí)現(xiàn)的靈活性。若共有類(lèi)暴露了它的數(shù)據(jù)域,由于程序的向前兼容,將來(lái)要改變它是不可能的。

對(duì)于私有嵌套類(lèi)或包級(jí)私有的類(lèi),允許直接暴露它的數(shù)據(jù)域。因?yàn)樗鼈兊姆轿欢急幌拗圃诹税鼉?nèi)部。

公有類(lèi)永遠(yuǎn)都不應(yīng)該暴露可變的域,暴露不可變的域危害較小

15、使可變性最小化

不可變類(lèi)是實(shí)例創(chuàng)建后不能被修改的類(lèi)。java類(lèi)庫(kù)中的不可變了包括String、基本數(shù)據(jù)類(lèi)型的包裝類(lèi)、BigInteger和BigDecimal。不可變類(lèi)易于設(shè)計(jì)和實(shí)現(xiàn)且更加安全。

使類(lèi)不可變,要遵循的規(guī)則:

  • 不要提供任何會(huì)修改對(duì)象狀態(tài)的方法(setter方法)
  • 保證類(lèi)不會(huì)被擴(kuò)展,使類(lèi)成為final的
  • 使所有的域都是private final的
  • 確保對(duì)于任何可變組件的互斥訪問(wèn)

例如:


public final class Complex {

    private final double re;//實(shí)部
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public double realPart() { return re; }
    public double imaginaryPart() { return im; }

    //兩個(gè)復(fù)數(shù)相加
    public Complex add(Complex c) {
        return new Complex(re + c.re, im + c.im); //新建實(shí)例
    }
}

在這個(gè)類(lèi)中,兩個(gè)復(fù)數(shù)相加返回一個(gè)新的Complex實(shí)例,而不是修改這個(gè)實(shí)例。大多數(shù)不可變類(lèi)都使用這種模式,它被稱(chēng)為函數(shù)的做法

不可變對(duì)象的優(yōu)點(diǎn):

  • 不可變對(duì)象是線程安全的,函數(shù)的做法這種模式使得不可變類(lèi)對(duì)象只有一種狀態(tài),即被創(chuàng)建時(shí)的狀態(tài)。
  • 不可變對(duì)象可以被自由的共享。不需要為不可變對(duì)象提供clone方法或拷貝構(gòu)造器,因?yàn)椴豢勺儗?duì)象是不變的,不存在被其他引用修改的可能?;谶@個(gè)原因客戶(hù)端應(yīng)該盡可能的重用不可變對(duì)象(可將常用對(duì)象聲明為公有的靜態(tài)final常量或?yàn)椴豢勺冾?lèi)提供靜態(tài)工廠方法)。

不可變對(duì)象的缺點(diǎn):對(duì)于每個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象,創(chuàng)建這些對(duì)象的代價(jià)可能很高,特別是對(duì)于大型對(duì)象。

若使用不可變對(duì)象執(zhí)行一個(gè)多步驟的操作,每個(gè)步驟都將產(chǎn)生一個(gè)新對(duì)象,除了最后的結(jié)果外的其它對(duì)象都將被丟棄,此時(shí)程序的性能會(huì)比較低。處理這個(gè)問(wèn)題的辦法是為這個(gè)不可變類(lèi)提供一個(gè)可變配套類(lèi)。例如,不可變類(lèi)String的公有配套類(lèi)StringBuilder是可變的。

為了確保不可變性,不可變類(lèi)絕對(duì)不允許自身被子類(lèi)化。實(shí)現(xiàn)這個(gè)限制的方法有①、使類(lèi)成為final的;②、讓類(lèi)所有的構(gòu)造器變?yōu)樗接械幕虬?jí)私有的并添加公有的靜態(tài)工廠方法來(lái)代替構(gòu)造器。

例如:


public class Complex {

    private final double re;//實(shí)部
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public double realPart() { return re; }
    public double imaginaryPart() { return im; }

    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }
}

對(duì)于處在包外的客戶(hù)端而言,上面這個(gè)類(lèi)實(shí)際上是final的,因?yàn)槿鄙賞ublic或protected構(gòu)造器的類(lèi)不能被繼承。(子類(lèi)的創(chuàng)建會(huì)先調(diào)用父類(lèi)的構(gòu)造器)。另外使用靜態(tài)工廠方法的優(yōu)點(diǎn)見(jiàn)第一條1、考慮用靜態(tài)工廠方法代替構(gòu)造器

忠告:

  • 不要為每個(gè)get方法編寫(xiě)一個(gè)相應(yīng)的set方法,除非有很好的理由讓類(lèi)成為可變的類(lèi)

  • 如果類(lèi)不能被做成不可變的,應(yīng)該盡可能的限制它的可變性,降低對(duì)象可以存在的狀態(tài)數(shù),這樣可以更容易的分析該對(duì)象的行為,同時(shí)降低出錯(cuò)的可能性。

16、復(fù)合優(yōu)先于繼承

繼承是實(shí)現(xiàn)代碼重用的有力手段,但它違背了封裝原則,使用不當(dāng)將會(huì)導(dǎo)致軟件變得很脆弱。在包內(nèi)使用繼承是非常安全的,因?yàn)檫@些代碼一般由一個(gè)程序員來(lái)編寫(xiě)。使用專(zhuān)門(mén)為繼承設(shè)計(jì)的類(lèi)也是安全的,然而對(duì)于普通的具體類(lèi)進(jìn)行跨包的繼承是非常危險(xiǎn)的(不包括接口的繼承和實(shí)現(xiàn)),這是因?yàn)樽宇?lèi)依賴(lài)于其超類(lèi)的具體實(shí)現(xiàn),當(dāng)超類(lèi)的實(shí)現(xiàn)隨版本變化時(shí),子類(lèi)可能會(huì)遭到破壞。

例如:為了程序調(diào)優(yōu)需要查詢(xún)HashSet類(lèi)自從被建立以來(lái)一共添加過(guò)多少個(gè)元素


public class MyHashSet<E> extends HashSet<E>{
    private static final long serialVersionUID = 1L;
    
    private int addCount = 0;
    
    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
    
    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
    
    public int getAddCount() {
        return addCount;
    }
    
    public static void main(String[] args) {
        MyHashSet<String> mHashSet = new MyHashSet<>();
        mHashSet.addAll(Arrays.asList("aaa","bbb","ccc"));
        System.out.println(mHashSet.getAddCount()); //輸出為6
    }
}

程序的輸出為6,而正確的結(jié)果應(yīng)該為3。哪里出錯(cuò)了呢?在HashSet的內(nèi)部,addAll方法是基于它的add方法來(lái)實(shí)現(xiàn)的,所以在MyHashSet中調(diào)用addAll方法首先將addCount增加3,然后調(diào)用父類(lèi)HashSet的addAll方法,根據(jù)多態(tài)其將會(huì)調(diào)用MyHashSet的add方法。因此每個(gè)元素被增加了兩次。雖然這個(gè)錯(cuò)誤可以通過(guò)重寫(xiě)addAll方法來(lái)遍歷集合消除,但是我們不能保證下個(gè)版本中HashSet的實(shí)現(xiàn)保持不變,MyHashSet這個(gè)類(lèi)是非常脆弱的。

使用復(fù)合(composition)可以有效的解決這個(gè)問(wèn)題,在新的類(lèi)中增加一個(gè)私有域來(lái)引用現(xiàn)有類(lèi)的一個(gè)實(shí)例,在這個(gè)類(lèi)中的實(shí)例方法通過(guò)調(diào)用被包含類(lèi)的實(shí)例方法返回結(jié)果。這樣的類(lèi)不依賴(lài)于現(xiàn)有類(lèi)具體的實(shí)現(xiàn)細(xì)節(jié),即使現(xiàn)有的類(lèi)添加了新的方法,也不會(huì)影響新的類(lèi)。

利用復(fù)合實(shí)現(xiàn)MyHashSet:


public class MyHashSet<E>{
    
    private int addCount = 0;
    private Set<E> mSet = null;
    
    public MyHashSet(Set<E> set) {
        this.mSet = set;
    }
    
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return mSet.addAll(c);
    }
    
    public boolean add(E e) {
        addCount++;
        return mSet.add(e);
    }
    
    public int getAddCount() {
        return addCount;
    }
    
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        MyHashSet<String> mHashSet = new MyHashSet<>(hashSet);
        mHashSet.addAll(Arrays.asList("aaa","bbb","ccc"));
        System.out.println(mHashSet.getAddCount()); //輸出為3
    }
}

利用復(fù)合能夠很好的實(shí)現(xiàn)現(xiàn)有類(lèi)與新建類(lèi)之間的解耦,增加了程序的靈活性與健壯性

注意:

  • 只有當(dāng)子類(lèi)真正是超類(lèi)的子類(lèi)型時(shí),才適用于繼承。即只有當(dāng)兩者之間確實(shí)存在“is-a”的關(guān)系時(shí),才用繼承。

  • 繼承機(jī)制會(huì)把超類(lèi)API中的所有缺陷傳播到子類(lèi)中去,而復(fù)合則允許設(shè)計(jì)新的API來(lái)隱藏這些缺陷。

17、要么為繼承而設(shè)計(jì),并提供文檔說(shuō)明,要么就禁止繼承

對(duì)于專(zhuān)門(mén)為了繼承而設(shè)計(jì)并且具有良好文檔說(shuō)明的類(lèi),繼承它是安全的。該類(lèi)的文檔必須精確的描述覆蓋每個(gè)方法所帶來(lái)的影響,更一般的類(lèi)必須在文檔中說(shuō)明,在哪些情況下它會(huì)調(diào)用可覆蓋方法。如果方法調(diào)用到了可覆蓋方法,在它的文檔注釋的末尾應(yīng)該包含關(guān)于這些調(diào)用的描述信息,通常這樣開(kāi)頭“This implementation... Note that...”

好的API文檔應(yīng)該描述一個(gè)給定的方法做了什么工作,而不是描述它是如何做到的。

上面的做法違背了這個(gè)原則,這正是繼承破壞了封裝性的后果。

注意:能被繼承的類(lèi)中構(gòu)造器決不能調(diào)用可被覆蓋的方法,無(wú)論是直接調(diào)用還是間接調(diào)用。
超類(lèi)的構(gòu)造器在子類(lèi)的構(gòu)造器之前運(yùn)行,子類(lèi)中覆蓋的方法將會(huì)在子類(lèi)的構(gòu)造器運(yùn)行之前被調(diào)用(多態(tài)),這可能會(huì)導(dǎo)致運(yùn)行失敗。

例如:


public final class Sub extends Super {
    private final Date date;
    
    Sub() {
        date = new Date();
    }
    
    @Override
    public void overrideMe() {
        System.out.println(date.getTime());
    }
    
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.overrideMe();
    }
}

class Super {
    public Super() {
        overrideMe();
    }
    
    public void overrideMe(){
        
    }
}

這段程序?qū)伋?code>NullPointerException異常,因?yàn)?code>overrideMe方法被Super構(gòu)造器調(diào)用時(shí),Sub構(gòu)造器還沒(méi)有初始化date,此時(shí)date為null。

在一個(gè)為了繼承而設(shè)計(jì)的類(lèi)中實(shí)現(xiàn)Cloneable接口或Serializable接口時(shí),也應(yīng)該遵循這樣的規(guī)則,即無(wú)論clone還是readObject中都不可以調(diào)用可覆蓋的方法,不管直接還是間接調(diào)用。因?yàn)閏lone和readObject的行為類(lèi)似于構(gòu)造器。

對(duì)于那些并非為了安全的進(jìn)行子類(lèi)化而設(shè)計(jì)和編寫(xiě)文檔的類(lèi),應(yīng)該禁止子類(lèi)化。一個(gè)好的替代方案是使用包裝類(lèi)來(lái)對(duì)現(xiàn)有類(lèi)進(jìn)行擴(kuò)展(要實(shí)現(xiàn)接口)。若具體的類(lèi)沒(méi)有實(shí)現(xiàn)接口而這個(gè)類(lèi)必須允許被繼承,一個(gè)合理的方法是確保這個(gè)類(lèi)永遠(yuǎn)也不會(huì)調(diào)用它的任何可覆蓋的方法,即消除這個(gè)類(lèi)中可覆蓋方法的自用性。

消除可覆蓋類(lèi)自用性的方法:

  • 將每個(gè)可覆蓋方法的代碼體移到一個(gè)私有的輔助方法中
  • 讓每個(gè)可覆蓋方法調(diào)用它的私有輔助方法
  • 在類(lèi)中使用輔助方法代替可覆蓋方法完成自我調(diào)用

例如:

class Super {
    public Super() {
        helperMethod();//使用輔助方法代替可覆蓋方法
    }
    public void overrideMe(){
        helperMethod();
    }
    
    //輔助方法
    private void helperMethod() {
        //具體實(shí)現(xiàn)
        ....
    }
}

18、接口優(yōu)于抽象類(lèi)

接口和抽象類(lèi)的區(qū)別:①、抽象類(lèi)允許包含某些方法的實(shí)現(xiàn),接口不允許;②、為實(shí)現(xiàn)由抽象類(lèi)定義的類(lèi)型,類(lèi)必須成為抽象類(lèi)的一個(gè)子類(lèi),而java中只允許單繼承,抽象類(lèi)作為類(lèi)型定義受到極大限制。對(duì)于接口任何類(lèi)都能實(shí)現(xiàn)。

接口優(yōu)于抽象類(lèi):

  • 接口比抽象類(lèi)更容易被使用。java是單繼承的。

  • 接口是定義mixin(混合類(lèi)型)的理想選擇。mixin類(lèi)型:類(lèi)除了實(shí)現(xiàn)它的“基本類(lèi)型”之外,還可以實(shí)現(xiàn)這個(gè)mixin類(lèi)型,以表明它提供了某些可供選擇的(額外的)行為。例如:Comparable是個(gè)mixin接口。抽象類(lèi)不能被用于定義mixin。

  • 接口允許我們構(gòu)造非層次結(jié)構(gòu)的類(lèi)型框架。一個(gè)類(lèi)可實(shí)現(xiàn)多個(gè)接口,也可以定義一個(gè)接口來(lái)繼承多個(gè)接口。

  • 接口可以使用包裝類(lèi)模式為現(xiàn)有類(lèi)增加功能,而抽象類(lèi)只能用繼承來(lái)增加功能

雖然接口不允許包含方法的實(shí)現(xiàn),但接口可以為類(lèi)的實(shí)現(xiàn)提供幫助。通過(guò)為每個(gè)重要接口提供一個(gè)抽象的骨架實(shí)現(xiàn)類(lèi),可以把接口和抽象類(lèi)的優(yōu)點(diǎn)結(jié)合起來(lái)。接口定義類(lèi)型,骨架類(lèi)定義接口基本的實(shí)現(xiàn)。例如,Collections框架的每個(gè)重要接口都有一個(gè)骨架實(shí)現(xiàn)類(lèi),包括AbstractCollection、AbstractSet、AbstractList、AbstractMap。骨架實(shí)現(xiàn)類(lèi)有助于接口的實(shí)現(xiàn),實(shí)現(xiàn)了這個(gè)接口的類(lèi)可以把對(duì)于接口方法的調(diào)用,轉(zhuǎn)發(fā)到一個(gè)內(nèi)部私有類(lèi)的實(shí)例上,這個(gè)內(nèi)部私有類(lèi)擴(kuò)展了骨架實(shí)現(xiàn)類(lèi)。這種方法被稱(chēng)作模擬多重繼承,這項(xiàng)技術(shù)具有多重繼承的絕大多數(shù)優(yōu)點(diǎn),同時(shí)避免了相應(yīng)的缺陷。

編寫(xiě)骨架實(shí)現(xiàn)類(lèi)步驟:

  • 認(rèn)真研究接口,并確定哪些方法是最基本的,其他方法可以根據(jù)它們來(lái)實(shí)現(xiàn)。這些基本方法將成為骨架實(shí)現(xiàn)類(lèi)的抽象方法。
  • 為接口中其他方法提供具體的實(shí)現(xiàn)。

例如:

public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V> {

    //基本方法
    public abstract K getKey();
    public abstract V getValue();

    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    @Override public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        return eq(getKey(), e.getKey()) && eq(getValue(), e.getValue());
    }
    
    private static boolean eq(Object o1, Object o2) {
        return o1 == null ? o2 == null : o1.equals(o2);
    }

    @Override public int hashCode() {
        return (getKey()   == null ? 0 :   getKey().hashCode()) ^
               (getValue() == null ? 0 : getValue().hashCode());
    }

    @Override public String toString() {
        return getKey() + "=" + getValue();
    }
}

其中將基本方法getKey()getValue()作為骨架實(shí)現(xiàn)類(lèi)的抽象方法。骨架實(shí)現(xiàn)類(lèi)是為繼承而設(shè)計(jì)的,所以應(yīng)該遵循第17條的規(guī)則。

簡(jiǎn)單實(shí)現(xiàn)類(lèi)同樣是為了繼承而設(shè)計(jì),并且是實(shí)現(xiàn)了接口,區(qū)別在于它不是抽象的,而是最簡(jiǎn)單的實(shí)現(xiàn)。

要想在公有接口中增加方法,而不破壞實(shí)現(xiàn)這個(gè)接口的所有現(xiàn)有類(lèi),這是不可能的。因此設(shè)計(jì)接口時(shí)要非常謹(jǐn)慎。接口一旦被公開(kāi)發(fā)行,并且被廣泛實(shí)現(xiàn),再想改變這個(gè)接口幾乎是不可能的。接口通常是定義允許多個(gè)實(shí)現(xiàn)的類(lèi)型的最佳途徑。但當(dāng)演變的容易性比靈活性和功能更為重要的時(shí)候,應(yīng)該使用抽象類(lèi)。因?yàn)槌橄箢?lèi)的演變比接口的演變要容易的多。

19、接口只用于定義類(lèi)型

接口應(yīng)該只被用來(lái)定義類(lèi)型,為任何其他目的而使用接口都是不當(dāng)?shù)摹?/p>

常量接口是對(duì)接口的不良使用。實(shí)現(xiàn)常量接口,會(huì)導(dǎo)致把類(lèi)內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)泄漏到類(lèi)的導(dǎo)出API中。為非final類(lèi)實(shí)現(xiàn)常量接口,它的子類(lèi)的命名空間就會(huì)被接口的常量“污染”。

導(dǎo)出常量的合理方案:

  • 若導(dǎo)出常量與某個(gè)現(xiàn)有類(lèi)或接口密切相關(guān),應(yīng)該把這些常量添加到這個(gè)類(lèi)或接口中。

  • 若導(dǎo)出常量最好被看做枚舉類(lèi)型的成員,就應(yīng)該使用枚舉類(lèi)型。

  • 否則使用不是實(shí)例化的工具類(lèi)。

public final class ConstantsUtility {
    private  ConstantsUtility() {}

    public static final double PI = 3.1415926;
    public static final int INIT_NUM = 1000;
}

若大量利用工具類(lèi)導(dǎo)出的常量,可使用靜態(tài)導(dǎo)入機(jī)制避免用類(lèi)名來(lái)修飾常量名。(jdk1.5后才引入)

import static com.alent.ConstantsUtility;
public class Test {
    double circleArea(double radius) {
        return PI*radius*radius;
    }
}

20、類(lèi)層次優(yōu)于標(biāo)簽類(lèi)

標(biāo)簽類(lèi)過(guò)于冗長(zhǎng)、容易出錯(cuò)并且效率低下。

例如:

class Figure {
    enum Shape { RECTANGLE, CIRCLE};

    final Shape shape;
    double length;
    doube width;
    double radius;

    Figure(double radius) {
        this.shape = Shape.CIRCLE;
        this.radius = radius;
    }

     Figure(double length, double width) {
        this.shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area() {
        switch(shape) {
            case RECTANGLE:
                return length*width;
            case CIRCLE:
                return Math.PI*(radius*radius);
            default:
                throw new AssertionError();
        }
    }
}

這種標(biāo)簽類(lèi),破壞了可讀性;域不能做出final的,除非構(gòu)造器初始化了不相關(guān)的域。

使用類(lèi)層次

interface Figure {
    double area();
}

class Circle implements Figure {
    final double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    double area() {
        return Math.PI*(radius*radius);
    }
}

class Rectangle implements Figure {
    final double length;
    final double width;
    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    double area() {
        return length*width;
    }
}

類(lèi)層次實(shí)現(xiàn)的優(yōu)點(diǎn):每個(gè)類(lèi)型的實(shí)現(xiàn)都配有自己的類(lèi),這些類(lèi)沒(méi)有受到不相關(guān)的數(shù)據(jù)域的拖累,所有的域都是final的。類(lèi)層次可以反映類(lèi)型之間本質(zhì)上的層次關(guān)系,有助于增強(qiáng)靈活性,并進(jìn)行更好的編譯時(shí)類(lèi)型檢查。

21、用函數(shù)對(duì)象表示策略

函數(shù)指針、代理、lambda表達(dá)式允許程序把“調(diào)用特殊函數(shù)的能力”存儲(chǔ)起來(lái)并進(jìn)行傳遞。這種機(jī)制通常用于允許函數(shù)的調(diào)用者通過(guò)傳入第二個(gè)函數(shù),來(lái)指定自己的行為。(策略模式)

java沒(méi)有函數(shù)指針,但可以用對(duì)象引用實(shí)現(xiàn)同樣的功能。定義一個(gè)對(duì)象,它的方法執(zhí)行其他對(duì)象(這個(gè)對(duì)象被顯式傳遞給這些方法)上的操作。這中對(duì)象被稱(chēng)為函數(shù)對(duì)象。

public interface Comparator<T> {
    public int compare(T t1, T t2);
}

class StringLengthComparator implements Comparator<String> {
    
}

//具體的策略類(lèi)往往使用匿名類(lèi)聲明
Arrays.sort(stringArray, new Comparator<String>() {
    public int compare(String s1, String s2){
        return s1.length() - s2.length();
    }
});

使用匿名內(nèi)部類(lèi)方式時(shí),每次執(zhí)行調(diào)用時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例。若它被重復(fù)執(zhí)行,可將函數(shù)對(duì)象存儲(chǔ)到一個(gè)私有的靜態(tài)final域里,并重用它。

例如:

class Host {
    private static class StrLenCmp implements Comparator<String>, Serializable{
        public int compare(String s1, String s2){
            return s1.length() - s2.length();
        }
    }

    public static final Comparator<String> STRIGN_LENGTH_COMPARATOR = new StrLenCmp();
}

簡(jiǎn)而言之,函數(shù)指針的主要用途是實(shí)現(xiàn)策略模式。為了在Java中實(shí)現(xiàn)這種模式,要聲明一個(gè)接口來(lái)表示該策略,并為每個(gè)具體策略聲明一個(gè)實(shí)現(xiàn)了該接口的類(lèi)。當(dāng)一個(gè)具體策略只被使用一次時(shí),通常使用匿名類(lèi)來(lái)聲明和實(shí)例化一個(gè)具體策略類(lèi)。當(dāng)一個(gè)具體策略是設(shè)計(jì)用來(lái)重復(fù)使用的時(shí)候,通常將類(lèi)實(shí)現(xiàn)為私有的靜態(tài)成員類(lèi),并通過(guò)公有的靜態(tài)final域被 導(dǎo)出,其類(lèi)型為該策略接口。

22、優(yōu)先考慮靜態(tài)成員類(lèi)

嵌套類(lèi)是指被定義在另一個(gè)類(lèi)內(nèi)部的類(lèi)。嵌套類(lèi)存在的目的:為它的外圍類(lèi)提供服務(wù)。嵌套類(lèi)有四種:靜態(tài)成員類(lèi)、非靜態(tài)成員類(lèi)、匿名類(lèi)和局部類(lèi)。除了靜態(tài)成員類(lèi),其它三種都被稱(chēng)為內(nèi)部類(lèi)。

1)、靜態(tài)成員類(lèi)最好把它看做普通的類(lèi),它可以訪問(wèn)外圍類(lèi)的所有成員,包括那些聲明為私有的成員。靜態(tài)成員類(lèi)是外圍類(lèi)的一個(gè)靜態(tài)成員,常作為公有的輔助類(lèi)。

2)、非靜態(tài)成員類(lèi)的每個(gè)實(shí)例都隱含著與外圍類(lèi)的一個(gè)實(shí)例相關(guān)聯(lián),在非靜態(tài)成員類(lèi)的實(shí)例方法內(nèi)部能夠調(diào)用外圍實(shí)例上的方法。在沒(méi)有外圍實(shí)例的情況下,不可能創(chuàng)建非靜態(tài)成員類(lèi)的實(shí)例。當(dāng)非靜態(tài)成員類(lèi)的實(shí)例被創(chuàng)建時(shí),它與外圍實(shí)例間的關(guān)聯(lián)關(guān)系隨之被建立,而且這種關(guān)聯(lián)關(guān)系以后不能被修改。

非靜態(tài)成員類(lèi)常用來(lái)定義一個(gè)Adapter。例如,Set和List集合接口的實(shí)現(xiàn)使用非靜態(tài)成員類(lèi)實(shí)現(xiàn)迭代器。

public class MySet<E> extends AbstractSet<E> {
    ....
    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ....
    }
}

若聲明的成員類(lèi)不需要訪問(wèn)外圍類(lèi),就應(yīng)該把它聲明為靜態(tài)成員類(lèi)。私有靜態(tài)成員類(lèi)常用來(lái)代表外圍類(lèi)所代表對(duì)象的組件。例如,Map內(nèi)部的Entry類(lèi),對(duì)應(yīng)于Map中的鍵值對(duì),若將Entry聲明為非靜態(tài)成員類(lèi),則每個(gè)Entry對(duì)象都將會(huì)包含一個(gè)指向該Map的引用,這將浪費(fèi)時(shí)間和空間。

3)、匿名類(lèi)使用有很多限制。①、無(wú)法聲明一個(gè)匿名類(lèi)來(lái)實(shí)現(xiàn)多個(gè)接口或擴(kuò)展一個(gè)類(lèi);②、匿名類(lèi)出現(xiàn)在表達(dá)式中,所以必須保持簡(jiǎn)潔(10行或更少),否則將影響可讀性。

匿名類(lèi)的應(yīng)用:①、創(chuàng)建函數(shù)對(duì)象;②、創(chuàng)建過(guò)程對(duì)象;③、用在靜態(tài)工廠方法的內(nèi)部

4)、局部類(lèi),在可以聲明局部變量的地方(方法內(nèi)部)都可以聲明局部類(lèi)。很少使用。

如果一個(gè)嵌套類(lèi)需要在單個(gè)方法之外仍然可見(jiàn),就不能使用局部類(lèi)。如果嵌套類(lèi)的每個(gè)實(shí)例都需要一個(gè)指向其外圍實(shí)例的引用,應(yīng)該使用非靜態(tài)成員類(lèi),否則就做成靜態(tài)成員類(lèi)。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,623評(píng)論 18 399
  • 1.使類(lèi)和成員的可訪問(wèn)性最小化 訪問(wèn)修飾符: private protected public 頂層的(非嵌套)類(lèi)...
    666真666閱讀 891評(píng)論 0 1
  • 類(lèi)和接口 一、使類(lèi)和成員的可訪問(wèn)性最小化 首先我們要了解一個(gè) 軟件設(shè)計(jì)基本原則:封裝 模塊隱藏所有的實(shí)現(xiàn)細(xì)節(jié),只通...
    dooze閱讀 532評(píng)論 0 0
  • 對(duì)象的創(chuàng)建與銷(xiāo)毀 Item 1: 使用static工廠方法,而不是構(gòu)造函數(shù)創(chuàng)建對(duì)象:僅僅是創(chuàng)建對(duì)象的方法,并非Fa...
    孫小磊閱讀 2,177評(píng)論 0 3
  • 01 難得知足 我有一個(gè)同學(xué),是一個(gè)很勤奮的人。 他手機(jī)里一大堆“得到”類(lèi)的APP,各種印象筆記,時(shí)間管理工具。他...
    院長(zhǎng)X大叔閱讀 10,064評(píng)論 84 414

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