類與接口是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)成員類。