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

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

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

設(shè)計(jì)良好的模塊會(huì)隱藏起所有的實(shí)現(xiàn)細(xì)節(jié)梅誓,僅使用API與其他模塊進(jìn)行通信。這個(gè)概念稱為信息隱藏或封裝,是軟件設(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è)類及成員的訪問(wèn)級(jí)別懂版。由于共有類是包的導(dǎo)出API的一部分鹃栽,可以被客戶端程序直接調(diào)用,對(duì)這種類的修改可能會(huì)影響程序向前的兼容性躯畴。而包級(jí)私有的類(默認(rèn)訪問(wèn)級(jí)別)是包實(shí)現(xiàn)的一部分民鼓,不會(huì)對(duì)外提供接口,在以后的發(fā)現(xiàn)版本中可以對(duì)其放心修改蓬抄。同樣對(duì)于公有類丰嘉,其public或protected成員是類的導(dǎo)出API的一部分,必須永遠(yuǎn)得到支持

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

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

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

長(zhǎng)度非零的數(shù)組總是可變的(例如观话,將元素null)予借,所以,若類具有公有的靜態(tài)final數(shù)組域,或返回這種域的方法灵迫,則客戶端將能夠修改其中的內(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、公有類中使用訪問(wèn)方法而非公有域

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

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

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

15现柠、使可變性最小化

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

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

  • 不要提供任何會(huì)修改對(duì)象狀態(tài)的方法(setter方法)
  • 保證類不會(huì)被擴(kuò)展,使類成為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è)類中万俗,兩個(gè)復(fù)數(shù)相加返回一個(gè)新的Complex實(shí)例湾笛,而不是修改這個(gè)實(shí)例。大多數(shù)不可變類都使用這種模式闰歪,它被稱為函數(shù)的做法

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

  • 不可變對(duì)象是線程安全的嚎研,函數(shù)的做法這種模式使得不可變類對(duì)象只有一種狀態(tài),即被創(chuàng)建時(shí)的狀態(tài)库倘。
  • 不可變對(duì)象可以被自由的共享临扮。不需要為不可變對(duì)象提供clone方法或拷貝構(gòu)造器,因?yàn)椴豢勺儗?duì)象是不變的教翩,不存在被其他引用修改的可能杆勇。基于這個(gè)原因客戶端應(yīng)該盡可能的重用不可變對(duì)象(可將常用對(duì)象聲明為公有的靜態(tài)final常量或?yàn)椴豢勺冾愄峁╈o態(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è)不可變類提供一個(gè)可變配套類膘掰。例如,不可變類String的公有配套類StringBuilder是可變的佳遣。

為了確保不可變性,不可變類絕對(duì)不允許自身被子類化凡伊。實(shí)現(xiàn)這個(gè)限制的方法有①零渐、使類成為final的;②系忙、讓類所有的構(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ì)于處在包外的客戶端而言,上面這個(gè)類實(shí)際上是final的银还,因?yàn)槿鄙賞ublic或protected構(gòu)造器的類不能被繼承风宁。(子類的創(chuàng)建會(huì)先調(diào)用父類的構(gòu)造器)。另外使用靜態(tài)工廠方法的優(yōu)點(diǎn)見(jiàn)第一條1蛹疯、考慮用靜態(tài)工廠方法代替構(gòu)造器

忠告:

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

  • 如果類不能被做成不可變的,應(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ě)理郑。使用專門為繼承設(shè)計(jì)的類也是安全的蹄溉,然而對(duì)于普通的具體類進(jìn)行跨包的繼承是非常危險(xiǎn)的(不包括接口的繼承和實(shí)現(xiàn)),這是因?yàn)樽宇愐蕾囉谄涑惖木唧w實(shí)現(xiàn)香浩,當(dāng)超類的實(shí)現(xiàn)隨版本變化時(shí)类缤,子類可能會(huì)遭到破壞。

例如:為了程序調(diào)優(yōu)需要查詢HashSet類自從被建立以來(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)用父類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è)類是非常脆弱的采郎。

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

利用復(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)有類與新建類之間的解耦,增加了程序的靈活性與健壯性

注意:

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

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

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

對(duì)于專門為了繼承而設(shè)計(jì)并且具有良好文檔說(shuō)明的類犹菇,繼承它是安全的德迹。該類的文檔必須精確的描述覆蓋每個(gè)方法所帶來(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è)原則姑原,這正是繼承破壞了封裝性的后果悬而。

注意:能被繼承的類中構(gòu)造器決不能調(diào)用可被覆蓋的方法,無(wú)論是直接調(diào)用還是間接調(diào)用锭汛。
超類的構(gòu)造器在子類的構(gòu)造器之前運(yùn)行笨奠,子類中覆蓋的方法將會(huì)在子類的構(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ì)的類中實(shí)現(xiàn)Cloneable接口或Serializable接口時(shí)乡范,也應(yīng)該遵循這樣的規(guī)則,即無(wú)論clone還是readObject中都不可以調(diào)用可覆蓋的方法啤咽,不管直接還是間接調(diào)用晋辆。因?yàn)閏lone和readObject的行為類似于構(gòu)造器。

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

消除可覆蓋類自用性的方法:

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

例如:

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

18盼玄、接口優(yōu)于抽象類

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

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

  • 接口比抽象類更容易被使用。java是單繼承的忽你。

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

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

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

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

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

  • 認(rèn)真研究接口,并確定哪些方法是最基本的蜘犁,其他方法可以根據(jù)它們來(lái)實(shí)現(xiàn)翰苫。這些基本方法將成為骨架實(shí)現(xiàn)類的抽象方法。
  • 為接口中其他方法提供具體的實(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)類的抽象方法奏窑。骨架實(shí)現(xiàn)類是為繼承而設(shè)計(jì)的,所以應(yīng)該遵循第17條的規(guī)則屈扎。

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

要想在公有接口中增加方法,而不破壞實(shí)現(xiàn)這個(gè)接口的所有現(xiàn)有類并村,這是不可能的巍实。因此設(shè)計(jì)接口時(shí)要非常謹(jǐn)慎。接口一旦被公開(kāi)發(fā)行哩牍,并且被廣泛實(shí)現(xiàn)棚潦,再想改變這個(gè)接口幾乎是不可能的。接口通常是定義允許多個(gè)實(shí)現(xiàn)的類型的最佳途徑膝昆。但當(dāng)演變的容易性比靈活性和功能更為重要的時(shí)候丸边,應(yīng)該使用抽象類叠必。因?yàn)槌橄箢惖难葑儽冉涌诘难葑円菀椎亩唷?/p>

19、接口只用于定義類型

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

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

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

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

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

  • 否則使用不是實(shí)例化的工具類辟犀。

public final class ConstantsUtility {
    private  ConstantsUtility() {}

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

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

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

20堂竟、類層次優(yōu)于標(biāo)簽類

標(biāo)簽類過(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)簽類跃捣,破壞了可讀性;域不能做出final的夺蛇,除非構(gòu)造器初始化了不相關(guān)的域。

使用類層次

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;
    }
}

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

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ì)象被稱為函數(shù)對(duì)象书劝。

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

class StringLengthComparator implements Comparator<String> {
    
}

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

使用匿名內(nè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)了該接口的類轴总。當(dāng)一個(gè)具體策略只被使用一次時(shí)司蔬,通常使用匿名類來(lái)聲明和實(shí)例化一個(gè)具體策略類署照。當(dāng)一個(gè)具體策略是設(shè)計(jì)用來(lái)重復(fù)使用的時(shí)候棚愤,通常將類實(shí)現(xiàn)為私有的靜態(tài)成員類譬淳,并通過(guò)公有的靜態(tài)final域被 導(dǎo)出直焙,其類型為該策略接口徘溢。

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

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

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

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

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

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

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

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

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窥翩,一起剝皮案震驚了整個(gè)濱河市业岁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寇蚊,老刑警劉巖笔时,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異仗岸,居然都是意外死亡允耿,警方通過(guò)查閱死者的電腦和手機(jī)借笙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)较锡,“玉大人业稼,你說(shuō)我怎么就攤上這事÷煸蹋” “怎么了低散?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)骡楼。 經(jīng)常有香客問(wèn)我熔号,道長(zhǎng),這世上最難降的妖魔是什么鸟整? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任引镊,我火速辦了婚禮,結(jié)果婚禮上吃嘿,老公的妹妹穿的比我還像新娘祠乃。我一直安慰自己,他們只是感情好兑燥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布亮瓷。 她就那樣靜靜地躺著,像睡著了一般降瞳。 火紅的嫁衣襯著肌膚如雪嘱支。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天挣饥,我揣著相機(jī)與錄音除师,去河邊找鬼。 笑死扔枫,一個(gè)胖子當(dāng)著我的面吹牛汛聚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播短荐,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼倚舀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了忍宋?” 一聲冷哼從身側(cè)響起痕貌,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糠排,沒(méi)想到半個(gè)月后舵稠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年哺徊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了室琢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唉工,死狀恐怖研乒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淋硝,我是刑警寧澤雹熬,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站谣膳,受9級(jí)特大地震影響竿报,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜继谚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一烈菌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧花履,春花似錦芽世、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至妹卿,卻和暖如春旺矾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夺克。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工箕宙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铺纽。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓柬帕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親狡门。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陷寝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

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