有些語言支持函數指針、代理、lambda表達式,或者支持類似的機制,允許程序把“調用特殊函數的能力”儲存起來並傳遞這種能力。這種機制通常用於允許函數的調用者通過傳入第二個函數,來指定自己的行為。比較器函數有兩個參數,都是指向元素的指針。如果第一個參數所指的元素小於第二個參數所指的元素,則返回一個負整數;如果兩個元素相等則返回零;如果第一個參數所指的元素大雨第二個,則返回一個正整數。通過傳遞不同的比較器函數,就可以獲得各種不同的排列順序。這正是策略模式的一個例子。比較器函數代表一種為元素排列的策略。
Java沒有提供函數指針,但是可以用對象引用實現同樣的功能。調用對象上的方法通常是執行該對象上的某個操作。然而,我們也可能定義這樣一種對象,它的方法執行其他對象上的操作。如果一個類僅僅導出這樣的一個方法,它的實例上就等同於一個指向該方法的指針。這樣的實例被稱為函數對象。考慮這樣一個類:
class StringLengthComparator { public int compare(String s1, String s2) { return s1.length() - s2.length(); } }
這個類導出一個帶兩個字符串參數的方法。指向StringLengthComparator對象的引用可以被當做是一個指向該比較器的“函數指針”,可以在任意一對字符串上被調用。換句話說,StringLengthComparator實例是用於字符串比較操作的具體策略。
作為典型的具體策略類,StringLengthComparator類是無狀態的:它沒有域,所以,這個類的所有實例在功能上是相互等價的。因此,它作為一個Singleton是非常合適的,可以節省不必要的對象創建開銷:
/** * 用函數對象表示策略 * @author weishiyao * */ public class StringLengthComparator { private StringLengthComparator() {} public static final StringLengthComparator INSTANCE = new StringLengthComparator(); public int compare(String s1, String s2) { return s1.length() - s2.length(); } }
為了把StringLengthComparator實例傳遞給方法,需要適當的參數類型。使用StringLengthComparator並不好,因為客戶端無法傳遞任何其他的比較策略。相反,我們需要定義一個Comparator接口,並修改StringLengthComparator來實現這個接口。換句話說,我們在設計具體的策略類時,還需要定義一個策略接口:
// Strategy interface public interface Comparator<T> { public int compare(T t1, T t2); }
Comparator接口的這個定義碰巧也出現在java.util包中,但是這並不神奇,我們自己也可以定義它。Comparator接口時范型的,因此它適合作為除字符串之外其他對象的比較器。它的compare兩個參數類型為T,而不是String。只要聲明前面所示的StringLengthComparator類要這麼做,就可以用它實現Comparator<String>接口。
具體的策略類往往使用匿名類聲明,下面的語句根據長度對一個字符串數組進行排序:
Arrays.sort(stringArray, new Comparator<T>() { @Override public int compare(String s1, String s2) { // TODO Auto-generated method stub return s1.length() - s2.length(); } });
但是注意,以這種方式使用匿名類時,將會在每次執行調用的時候創建一個新的實例。如果它被重復執行,考慮將函數對象存儲到一個私有的靜態final域裡,並重用它。這樣做的另一種好處是,可以為這個函數對象取一個有意義的域名。
因為策略接口被用作所有具體策略實例的類型,所以並不需要為了倒出具體策略,而把策略類做成公有的。相反“宿主類”還可以導出公有的靜態域,其類型為策略接口,具體的策略類可以是宿主類的私有前套類。下面的例子使用靜態成員類,而不是匿名類,以便允許具體的策略類實現第二個接口Serializable
/** * 用函數對象表示策略 * @author weishiyao * */ public class Host { private static class StrLenCmp implements Comparator<String>, Serializable { /** * */ private static final long serialVersionUID = -5797980299250787300L; public int compare(String s1, String s2) { return s1.length() - s2.length(); } } // Return comparator is serializable public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp(); public static void main(String[] args) { System.out.println(STRING_LENGTH_COMPARATOR.compare("aaaaaa", "aaaaa")); } }
String類利用這種模式,通過它的STRING_LENGTH_COMPARATOR域,導出一個不區分大小寫的字符串比較器。
總而言之,函數指針的主要用途就是實現策略模式。為了在java中實現這種模式,要聲明一個接口來表示該策略,並且為每一個策略聲明一個實現了該接口的類。當一個具體策略只被使用一次時,通常使用匿名類來聲明和實例化這個具體策略類。當一個具體策略類時設計用來重復使用的時候,它的類通常就要被實現為私有的靜態成員類,並通過公有的靜態final域被導出,其類型為該策略接口。