十二、考慮實作Comparable接口:
和之前提到的通用方法equals、hashCode和toString不同的是compareTo方法屬于Comparable接口,該接口為其實作類提供了排序比較的規則,實作類僅需基于内部的邏輯,為compareTo傳回不同的值,既A.compareTo(B) > 0可視為A > B,反之則A < B,如果A.compareTo(B) == 0,可視為A == B。在C++中由于提供了操作符重載的功能,是以可以直接通過重載操作符的方式進行對象間的比較,事實上C++的标準庫中提供的預設規則即為此,如bool operator>(OneObject o)。在Java中,如果對象實作了Comparable接口,即可充分利用JDK集合架構中提供的各種泛型算法,如:Arrays.sort(a); 即可完成a對象數組的排序。事實上,JDK中的所有值類均實作了該接口,如Integer、String等。
Object.equals方法的通用實作準則也同樣适用于Comparable.compareTo方法,如對稱性、傳遞性和一緻性等,這裡就不做過多的贅述了。然而兩個方法之間有一點重要的差異還是需要在這裡提及的,既equals方法不應該抛出異常,而compareTo方法則不同,由于在該方法中不推薦跨類比較,如果目前類和參數對象的類型不同,可以抛出ClassCastException異常。在JDK 1.5 之後我們實作的Comparable<T>接口多為該泛型接口,不在推薦直接繼承1.5 之前的非泛型接口Comparable了,新的compareTo方法的參數也由Object替換為接口的類型參數,是以在正常調用的情況下,如果參數類型不正确,将會直接導緻編譯錯誤,這樣有助于開發者在coding期間修正這種由類型不比對而引發的異常。
在該條目中針對compareTo的相等性比較給出了一個強烈的建議,而不是真正的規則。推薦compareTo方法施加的等同性測試,在通常情況下應該傳回和equals方法同樣的結果,考慮如下情況:
public static void main(String[] args) {
HashSet<BigDecimal> hs = new HashSet<BigDecimal>();
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
hs.add(bd1);
hs.add(bd2);
System.out.println("The count of the HashSet is " + hs.size());
TreeSet<BigDecimal> ts = new TreeSet<BigDecimal>();
ts.add(bd1);
ts.add(bd2);
System.out.println("The count of the TreeSet is " + ts.size());
}
/* 輸出結果如下:
The count of the HashSet is 2
The count of the TreeSet is 1
*/
由以上代碼的輸出結果可以看出,TreeSet和HashSet中包含元素的數量是不同的,這其中的主要原因是TreeSet是基于BigDecimal的compareTo方法是否傳回0來判斷對象的相等性,而在該例中compareTo方法将這兩個對象視為相同的對象,是以第二個對象并未實際添加到TreeSet中。和TreeSet不同的是HashSet是通過equals方法來判斷對象的相同性,而恰恰巧合的是BigDecimal的equals方法并不将這個兩個對象視為相同的對象,這也是為什麼第二個對象可以正常添加到HashSet的原因。這樣的差異确實給我們的程式設計帶來了一定的負面影響,由于HashSet和TreeSet均實作了Set<E>接口,倘若我們的集合是以Set<E>的參數形式傳遞到目前添加BigDecimal的函數中,函數的實作者并不清楚參數Set的具體實作類,在這種情況下不同的實作類将會導緻不同的結果發生,這種現象極大的破壞了面向對象中的"裡氏替換原則"。
在重載compareTo方法時,應該将最重要的域字段比較方法比較的最前端,如果重要性相同,則将比較效率更高的域字段放在前面,以提高效率,如以下代碼:
public int compareTo(PhoneNumer pn) {
if (areaCode < pn.areaCode)
return -1;
if (areaCode > pn.areaCode)
return 1;
if (prefix < pn.prefix)
if (prefix > pn.prefix)
if (lineNumber < pn.lineNumer)
if (lineNumber > pn.lineNumber)
return 0;
上例給出了一個标準的compareTo方法實作方式,由于使用compareTo方法排序的對象并不關心傳回的具體值,隻是判斷其值是否大于0,小于0或是等于0,是以以上方法可做進一步優化,然而需要注意的是,下面的優化方式會導緻數值類型的作用域溢出問題。
int areaCodeDiff = areaCode - pn.areaCode;
if (areaCodeDiff != 0)
return areaCodeDiff;
int prefixDiff = prefix - pn.prefix;
if (prefixDiff != 0)
return prefixDiff;
int lineNumberDiff = lineNumber - pn.lineNumber;
if (lineNumberDiff != 0)
return lineNumberDiff;
十三、使類和成員的可通路性最小化:
資訊隐藏是軟體程式設計的基本原則之一,面向對象又為這一設計原則提供了有力的支援和保障。這裡我們簡要列出幾項受益于該原則的優勢:
1. 更好的解除各個子產品之間的耦合關系:
由于子產品間的互相調用是基于接口契約的,每個子產品隻是負責完成自己内部既定的功能目标和單元測試,一旦今後出現性能優化或需求變更時,我們首先需要做的便是定位需要變動的單個子產品或一組子產品,然後再針對各個子產品提出各自的解決方案,分别予以改動和内部測試。這樣便大大降低了因代碼無規則交叉而帶來的潛在風險,同時也縮減了開發周期。
2. 最大化并行開發:
由于各個子產品之間保持着較好的獨立性,是以可以配置設定更多的開發人員同時實作更多的子產品,由于每個人都是将精力完全集中在自己負責和擅長的專一領域,這樣不僅提高了軟體的品質,也大大加快了開發的進度。
3. 性能優化和後期維護:
一般來說,局部優化的難度和可行性總是要好于來自整體的優化,事雖如此,然而我們首先需要做的卻是如何定位需要優化的局部,在設計良好的系統中,完成這樣的工作并非難事,我們隻需針對每個涉及的子產品做性能和壓力測試,之後再針對測試的結果進行分析并拿到相對合理的解決方案。
4. 代碼的高可複用性:
在軟體開發的世界中,提出了衆多的設計理論,設計原則和設計模式,之是以這樣,一個非常現實的目标之一就是消除重複代碼,記得《重構》中有這樣的一句話:“重複代碼,萬惡之源”。可見提高可用代碼的複用性不僅對程式設計效率和産品品質有着非常重要的意義,對日後産品的更新和維護也是至關重要的。說一句比較現實的話,一個設計良好的産品,即使因為某些原因導緻失敗,那麼産品中應用到的一個個獨立、可用和高效的子產品也為今後的東山再起提供了一個很好的技術基礎。
讓我們重新回到主題,Java通過通路控制的方式來完成資訊隐藏,而我們的原則是盡可能的使每個類的域成員不被外界通路。對于包内的類而言,則盡可能少的定義公有類,遵循這樣的原則可以極大的降低因包内設計或實作的改變而給該包的使用者帶來的影響。當然達到這個目标的一個重要前提是定義的接口足以完成調用者的需求。
該條目給出了一個比較重要的建議,既不要提供直接通路或通過函數傳回可變域對象的執行個體,見下例:
public final Thing[] values = { ... };
即便Thing數組對象本身是final的,不能再被指派給其他對象,然而數組内的元素是可以改變的,這樣便給外部提供了一個機會來修改内部資料的狀态,進而在主類未知的情況下破壞了對象内部的狀态或資料的一緻性。其修訂方式如下:
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
總而言之,你應該盡可能地降低可通路性。你在仔細地設計了一個最小的公有API之後,應該防止把任何散亂的類、接口和成員變成API的一部分。除了公有靜态final域的特殊情形之外,公有類都不應該包含公有域。并且要確定公有靜态final域所引用的對象都是不可變的。
十四、在公有類中使用通路方法而非公有域:
這個條目簡短的标題已經非常清晰的表達了他的含義,我們這裡将隻是列出幾點說明:
1. 對于公有類而言,由于存在大量的使用者,是以修改API接口将會給使用者帶來極大的不便,他們的代碼也需要随之改變。如果公有類直接暴露了域字段,一旦今後需要針對該域字段添加必要的限制邏輯時,唯一的方法就是為該字段添加通路器接口,而已有的使用者也将不得不更新其代碼,以避免破壞該類的内部邏輯。
2. 對于包級類和嵌套類,公有的域方法由于隻能在包内可以被通路,因而修改接口不會給包的使用者帶來任何影響。
3. 對于公有類中的final域字段,提供直接通路方法也會帶來負面的影響,隻是和非final對象相比可能會稍微好些,如final的數組對象,即便數組對象本身不能被修改,但是他所包含的數組成員還是可以被外部改動的,針對該情況建議提供API接口,在該接口中可以添加必要的驗證邏輯,以避免非法資料的插入,如:
public <T> boolean setXxx(int index, T value) {
if (index > myArray.length)
return false;
if (!(value instanceof LegalClass))
...
return true;
十五、使可變性最小化:
隻在類構造的時候做初始化,構造之後類的外部沒有任何方法可以修改類成員的狀态,該對象在整個生命周期内都會保持固定不變的狀态,如String、Integer等。不可變類比可變類更加易于設計、實作和使用,而且線程安全。
使類成為不可變類應遵循以下五條原則:
1. 不要提供任何會修改對象狀态的方法;
2. 保證類不會被擴充,既聲明為final類,或将構造函數定義為私有;
3. 使所有的域都是final的;
4. 使所有的域都成為私有的;
5. 確定在傳回任何可變域時,傳回該域的deep copy。
見如下Complex類:
final class Complex {
private final double re;
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;
public Complex add(Complex c) {
return new Complex(re + c.re,im + c.im);
public Complex substract(Complex c) {
return new Complex(re - c.re, im - c.im);
... ...
不可變對象還有一個對象重用的優勢,這樣可以避免建立多餘的新對象,這樣也能減輕垃圾收集器的壓力,如:
public static final Complex ZERO = new Complex(0,0);
public static final Complex ONE = new Complex(1,0);
這樣使用者可以重複使用上面定義的兩個靜态final類,而不需要在每次使用時都建立新的對象。
從Complex.add和Complex.substract兩個方法可以看出,每次調用他們的時候都會有新的對象被建立,這樣勢必會帶來一定的性能影響,特别是對于copy開銷比較大的對象,如包含幾萬Bits的BigInteger。如果我們所作的操作僅僅是修改其中的某個Bit,如bigInteger.flipBit(0),該操作隻是修改了第0位的狀态,而BigInteger卻為此copy了整個對象并傳回。鑒于此,該條目推薦為不可變對象提供一個功能相仿的可變類,如java.util.BitSet之于java.math.BigInteger。如果我們在實際開發中确實遇到剛剛提及的場景,那麼使用BitSet或許是更好的選擇。
對于不可變對象還有比較重要的優化技巧,既某些關鍵值的計算,如hashCode,可以在對象構造時或留待某特定方法(Lazy Initialization)第一次調用時進行計算并緩存到私有域字段中,之後再擷取該值時,可以直接從該域字段擷取,避免每次都重新計算。這樣的優化主要是依賴于不可變對象的域字段在構造後即保持不變的特征。
十六、複合優先于繼承:
由于繼承需要透露一部分實作細節,是以不僅需要超類本身提供良好的繼承機制,同時也需要提供更好的說明文檔,以便子類在覆寫超類方法時,不會引起未知破壞行為的發生。需要特别指出的是對于跨越包邊界的繼承,很可能超類和子類的實作者并非同一開發人員或同一開發團隊,是以對于某些依賴實作細節的覆寫方法極有可能會導緻預料之外的結果,還需要指出的是,這些細節對于超類的普通使用者來說往往是不看見的,是以在未來的更新中,該實作細節仍然存在變化的可能,這樣對于子類的實作者而言,在該細節變化時,子類的相關實作也需要做出必要的調整,見如下代碼:
//這裡我們需要擴充HashSet類,提供新的功能用于統計目前集合中元素的數量,
//實作方法是新增一個私有域變量用于儲存元素數量,并每次添加新元素的方法中
//更新該值,再提供一個公有的方法傳回該值。
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;
該子類覆寫了HashSet中的兩個方法add和addAll,而且從表面上看也非常合理,然而他卻不能正常的工作,見下面的測試代碼:
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap","Crackle","Pop"));
System.out.println("The count of InstrumentedHashSet is " + s.getAddCount());
//The count of InstrumentedHashSet is 6
從輸出結果中可以非常清楚的看出,我們得到的結果并不是我們期望的3,而是6。這是什麼原因所緻呢?在HashSet的内部,addAll方法是基于add方法來實作的,而HashSet的文檔中也并未列出這樣的細節說明。了解了原因之後,我們應該取消addAll方法的覆寫,以保證得到正确的結果。然而仍然需要指出的是,這樣的細節既然未在API文檔中予以說明,那麼也就間接的表示這種未承諾的實作邏輯是不可依賴的,因為在未來的某個版本中他們有可能會發生悄無聲息的發生變化,而我們也無法通過API文檔獲悉這些。還有一種情況是超類在未來的版本中新增了添加新元素的接口方法,是以我們在子類中也必須覆寫這些方法,同時也要注意一些新的超類實作細節。由此可見,類似的繼承是非常脆弱的,那麼該如何修訂我們的設計呢?答案很簡單,複合優先于繼承,見如下代碼:
//轉發類
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 void clear() {
s.clear();
return s.add(e);
return s.addAll(c);
//包裝類
class InstrumentedHashSet<E> extends ForwardingSet<E> {
由上面的代碼可以看出,這種設計最大的問題就是比較瑣碎,需要将接口中的方法基于委托類重新實作。
在決定使用繼承而不是複合之間,還應該問自己最後一組問題。對于你試圖擴充的類,它的API中有沒有缺陷呢?如果有,你是否願意把這些缺陷傳播到類的API中?繼承機制會把超類API中的所有缺陷傳播到子類中,而複合則允許設計新的API來隐藏這些缺陷。
十七、要麼為繼承而設計,并提供文檔說明,要麼就禁止繼承:
上一條目針對繼承将會引發的潛在問題給出了很好的解釋,本條目将繼續深化這一個設計理念,并提出一些好的建議,以便在确實需要基于繼承來設計時,避免這些潛在問題的發生。
1) 為公有方法提供更為詳細的說明文檔,這其中不僅包擴必要的功能說明和參數描述,還要包含關鍵的實作細節說明,比如對其他公有方法的依賴和調用。
在上一條目的代碼示例中,子類同時覆寫了HashSet的addAll和add方法,由于二者之間存在内部的調用關系,而API文檔中并沒有給出詳細的說明,因而子類的覆寫方法并沒有得到期望的結果。
2) 在超類中盡可能避免公有方法之間的互相調用。
HashSet.addAll和HashSet.add給我們提供了一個很好的案例,然而這并不表示HashSet的設計和實作是有問題的,我們隻能說HashSet不是為了繼承而設計的類。在實際的開發中,如果确實有這樣的需要又該如何呢?很簡單,将公用的代碼提取(extract)到一個私有的幫助方法中,再在其他的公有方法中調用該幫助方法。
3) 可以采用設計模式中模闆模式的設計技巧,在超類中将需要被覆寫的方法設定為protected級别。
在采用這種方式設計超類時,還需要額外考慮的是哪些域字段也同時需要被設定為protected級别,以保證子類在覆寫protected方法時,可以得到必要的狀态資訊。
4) 不要在超類的構造函數中調用可能被子類覆寫的方法,如public和protected級别的域方法。
由于超類的初始化早于子類的初始化,如果此時調用的方法被子類覆寫,而覆寫的方法中又引用了子類中的域字段,這将很容易導緻NullPointerException異常被抛出,見下例:
public class SuperClass {
public SuperClass() {
overrideMe();
public void overrideMe() {}
public final class SubClass extends SuperClass {
private final Date d;
SubClass() {
d = new Date();
@Override public void overrideMe() {
System.out.println(dd.getDay());
public static void main(String[] args) {
SubClass sub = new SubClass();
sub.overrideMe();
5) 如果超類實作了Cloneable和Serializable接口,由于clone和readObject也有構造的能力,是以在實作這兩個接口方法時也需要注意,不能調用子類的覆寫方法。
十八、接口優先于抽象類:
衆所周知,Java是不支援多重繼承但是可以實作多個接口的,而這也恰恰成為了接口優于抽象類的一個重要因素。現将他們的主要差異列舉如下:
1) 現有的類可以很容易被更新,以實作新的接口。
如果現存的類并不具備某些功能,如比較和序列化,那麼我們可以直接修改該類的定義分别實作Comparable和Serializable接口。倘若Comparable和Serializable不是接口而是抽象類,那麼同時繼承兩個抽象類是Java文法規則所不允許的,如果目前類已經繼承自某個超類了,那麼他将無法再擴充任何新的超類。
2) 接口是定義mixin(混合類型)的理想選擇。
Comparable是一個典型的mixin接口,他允許類表明他的執行個體可以與其他的可互相比較的對象進行排序。這樣的接口之是以被稱為mixin,是因為他允許任選的功能可被混合到類型的主要功能中。抽象類不能被用于定義mixin,同樣也是因為他們不能被更新到現有的類中:類不可能有一個以上的超類,類層次結構中也沒有适當的地方來插入mixin。
3) 接口允許我們構造非層次結構的類型架構。
由于我們可以為任何已有類添加新的接口,而無需考慮他目前所在架構中的類層次關系,這樣便給功能的擴充帶來了極大的靈活性,也減少了對已有類層次的沖擊。如:
public interface Singer { //歌唱家
AudioClip sing(Song s);
public interface SongWriter { //作曲家
Song compose(boolean hit);
在現實生活中,有些歌唱家本身也是作曲家。因為我們這裡是通過接口來定義這兩個角色的,所有同時實作他們是完全可能的。甚至可以再提供一個接口擴充自這兩個接口,并提供新的方法,如:
public interface SingerWriter extends Singer, SongWriter {
AudioClip strum();
void actSensitive();
試想一下,如果将Singer和SongWriter定義為抽象類,那麼完成這一擴充就會是非常浩大的工程,甚至可能造成"組合爆炸"的現象。
我們已經列舉出了一些接口和抽象類之間的重要差異,下面我們還可以了解一下如何組合使用接口和抽象類,以便他們能為我們設計的架構帶來更好的擴充性和層級結構。在Java的Collections Framework中存在一組被稱為"骨架實作"(skeletal implementation)的抽象類,如AbstractCollection、AbstractSet和AbstractList等。如果設計得當,骨架實作可以使程式員很容易的提供他們自己的接口實作。這種組合還可以讓我們在設計自己的類時,根據實際情況選擇是直接實作接口,還是擴充該抽象類。和接口相比,骨架實作類還存在一個非常明顯的優勢,既如果今後為該骨架實作類提供新的方法,并提供了預設的實作,那麼他的所有子類均不會受到影響,而接口則不同,由于接口不能提供任何方法實作,是以他所有的實作類必須進行修改,為接口中新增的方法提供自己的實作,否則将無法通過編譯。
簡而言之,接口通常是定義允許多個實作的類型的最佳途徑。這條規則有個例外,即當演變的容易性比靈活性更為重要的時候。在這種情況下,應該使用抽象類來定義類型,但前提是必須了解并且可以接受這些局限性。如果你導出了一個重要的接口,就應該堅決考慮同時提供骨架實作類。
十九、接口隻用于定義類型:
當類實作接口時,接口就充當可以引用這個類的執行個體的類型。是以,類實作了接口,就表明用戶端可以對這個類的執行個體實施某些動作。為了任何其他目的定義接口是不恰當的。如實作Comparable接口的類,表明他可以存放在排序的集合中,之後再從集合中将存入的對象有序的讀出,而實作Serializable接口的類,表明該類的對象具有序列化的能力。類似的接口在JDK中大量存在。
二十、類層次優于标簽類:
這裡先給出标簽類的示例代碼:
class Figure {
enum Shape { RECT,CIRCLE };
final Shape s; //标簽域字段,辨別目前Figure對象的實際類型RECT或CIRCLE。
double length; //length和width均為RECT形狀的專有域字段
double width;
double radius; //radius是CIRCLE的專有域字段
Figure(double radius) { //專為生成CIRCLE對象的構造函數
s = Shape.CIRCLE;
this.radius = radius;
Figure(double length,double width) { //專為生成RECT對象的構造函數
s = Shape.RECT;
this.length = length;
this.width = width;
double area() {
switch (s) { //存在大量的case判斷來确定實際的對象類型。
case RECT:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
像Figure這樣的類通常被我們定義為标簽類,他實際包含多個不同類的邏輯,其中每個類都有自己專有的域字段和類型辨別,然而他們又都同屬于一個标簽類,是以被混亂的定義在一起。在執行真正的功能邏輯時,如area(),他們又不得不通過case語句再重新進行劃分。現在我們總結一下标簽類将會給我們的程式帶來哪些負面影響。
1. 不同類型執行個體要求的域字段被定義在同一個類中,不僅顯得混亂,而且在構造新對象執行個體時,也會加大記憶體的開銷。
2. 初始化不統一,從上面的代碼中已經可以看出,在專為建立CIRCLE對象的構造函數中,并沒有提供length和width的初始化功能,而是借助了JVM的預設初始化。這樣會給程式今後的運作帶來潛在的失敗風險。
3. 由于沒有在構造函數中初始化所有的域字段,是以不能将所有的域字段定義為final的,這樣該類将有可能成為可變類。
4. 大量的swtich--case語句,在今後添加新類型的時候,不得不修改area方法,這樣便會引發因誤修改而造成錯誤的風險。順便說一下,這一點可以被看做《靈活軟體開發》中OCP原則的反面典型。
那麼我們需要通過什麼方法來解決這樣的問題呢?該條目給出了明确的答案:利用Java語句提供的繼承功能。見下面的代碼:
abstract class Figure {
abstract double area();
class Circle extends Figure {
final double radius;
Circle(double radius) {
return Math.PI * (radius * radius);
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length,double width) {
return length * width;
現在我們為每種标簽類型都定義了不同的子類,可以明顯看出,這種基于類層次的設計規避了标簽類的所有問題,同時也大大提供了程式的可讀性和可擴充性,如:
class Square extends Rectangle {
Square(double side) {
super(side,side);
現在我們新增了正方形類,而我們所需要做的僅僅是繼承Rectangle類。
簡而言之,标簽類很少有适用的場景。當你想要編寫一個包含顯式标簽域的類時,應該考慮一下,這個标簽是否可以被取消,這個類是否可以用類層次來代替。當你遇到一個包含标簽域的現有類時,就要考慮将它重構到一個層次結構中去。
二十一、用函數對象表示政策:
函數對象可以簡單的了解為C語言中的回調函數,但是我想他更加類似于C++中的仿函數對象。仿函數對象在C++的标準庫中(STL)有着廣泛的應用,如std::less等。在Java中并未提供這樣的文法規則,是以他們在實作技巧上确實存在一定的差異,然而設計理念卻是完全一緻的。下面是該條目中對函數對象的描述:
Java沒有提供函數指針,但是可以用對象引用實作統一的功能。調用對象上的方法通常是執行該對象(that Object)上的某項操作。然而,我們也可能定義這樣一種對象,它的方法執行其他對象(other Objects)上的操作。如果一個類僅僅導出這樣的一個方法,它的執行個體實際上就等同于一個指向該方法的指針。這樣的執行個體被稱為函數對象(Function Object),如JDK中Comparator,我們可以将該對象看做是實作兩個對象之間進行比較的"具體政策對象",如:
class StringLengthComparator {
public int compare(String s1,String s2) {
return s1.length() - s2.length();
這種對象自身并不包含任何域字段,其所有執行個體在功能上都是等價的,是以可以看作為無狀态的對象。這樣為了提供系統的性能,避免不必要的對象建立開銷,我們可以将該類定義為Singleton對象,如:
private StringLengthComparator() {} //禁止外部執行個體化該類
public static final StringLengthComparator INSTANCE = new StringLengthComparator();
StringLengthComparator類的定義極大的限制了參數的類型,這樣用戶端也無法再傳遞任何其他的比較政策。為了修正這一問題,我們需要讓該類成為Comparator<T>接口的實作類,由于Comparator<T>是泛型類,是以我們可以随時替換政策對象的參數類型,如:
class StringLengthComparator implements Comparator<String> {
簡而言之,函數指針的主要用途就是實作政策模式。為了在Java中實作這種模式,要聲明一個接口來表示政策,并且為每個具體政策聲明一個實作了該接口的類。當一個具體政策隻被使用一次時,可以考慮使用匿名類來聲明和執行個體化這個具體的政策類。當一個具體政策是設計用來重複使用的時候,他的類通常就要被實作為私有的靜态成員類,并通過公有的靜态final域被導出,其類型為該政策接口。