現在假設我們想觀看一個目錄列表。可用兩種方式列出File對象。若在不含自變量(參數)的情況下調用list(),會獲得File對象包含的一個完整列表。然而,若想對這個列表進行某些限制,就需要使用一個“目錄過濾器”,該類的作用是指出應如何選擇File對象來完成顯示。
下面是用於這個例子的代碼(或在執行該程序時遇到困難,請參考第3章3.1.2小節“賦值”):
//: DirList.java // Displays directory listing package c10; import java.io.*; public class DirList { public static void main(String[] args) { try { File path = new File("."); String[] list; if(args.length == 0) list = path.list(); else list = path.list(new DirFilter(args[0])); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } catch(Exception e) { e.printStackTrace(); } } } class DirFilter implements FilenameFilter { String afn; DirFilter(String afn) { this.afn = afn; } public boolean accept(File dir, String name) { // Strip path information: String f = new File(name).getName(); return f.indexOf(afn) != -1; } } ///:~
DirFilter類“實現”了interface FilenameFilter(關於接口的問題,已在第7章進行了詳述)。下面讓我們看看FilenameFilter接口有多麼簡單:
public interface FilenameFilter {
boolean accept(文件目錄, 字串名);
}
它指出這種類型的所有對象都提供了一個名為accept()的方法。之所以要創建這樣的一個類,背後的全部原因就是把accept()方法提供給list()方法,使list()能夠“回調”accept(),從而判斷應將哪些文件名包括到列表中。因此,通常將這種技術稱為“回調”,有時也稱為“算子”(也就是說,DirFilter是一個算子,因為它唯一的作用就是容納一個方法)。由於list()采用一個FilenameFilter對象作為自己的自變量使用,所以我們能傳遞實現了FilenameFilter的任何類的一個對象,用它決定(甚至在運行期)list()方法的行為方式。回調的目的是在代碼的行為上提供更大的靈活性。
通過DirFilter,我們看出盡管一個“接口”只包含了一系列方法,但並不局限於只能寫那些方法(但是,至少必須提供一個接口內所有方法的定義。在這種情況下,DirFilter構建器也會創建)。
accept()方法必須接納一個File對象,用它指示用於尋找一個特定文件的目錄;並接納一個String,其中包含了要尋找之文件的名字。可決定使用或忽略這兩個參數之一,但有時至少要使用文件名。記住list()方法准備為目錄對象中的每個文件名調用accept(),核實哪個應包含在內——具體由accept()返回的“布爾”結果決定。
為確定我們操作的只是文件名,其中沒有包含路徑信息,必須采用String對象,並在它的外部創建一個File對象。然後調用getName(),它的作用是去除所有路徑信息(采用與平台無關的方式)。隨後,accept()用String類的indexOf()方法檢查文件名內部是否存在搜索字串"afn"。若在字串內找到afn,那麼返回值就是afn的起點索引;但假如沒有找到,返回值就是-1。注意這只是一個簡單的字串搜索例子,未使用常見的表達式“通配符”方案,比如"fo?.b?r*";這種方案更難實現。
list()方法返回的是一個數組。可查詢這個數組的長度,然後在其中遍歷,選定數組元素。與C和C++的類似行為相比,這種於方法內外方便游歷數組的行為無疑是一個顯著的進步。
1. 匿名內部類
下例用一個匿名內部類(已在第7章講述)來重寫顯得非常理想。首先創建了一個filter()方法,它返回指向FilenameFilter的一個句柄:
//: DirList2.java // Uses Java 1.1 anonymous inner classes import java.io.*; public class DirList2 { public static FilenameFilter filter(final String afn) { // Creation of anonymous inner class: return new FilenameFilter() { String fn = afn; public boolean accept(File dir, String n) { // Strip path information: String f = new File(n).getName(); return f.indexOf(fn) != -1; } }; // End of anonymous inner class } public static void main(String[] args) { try { File path = new File("."); String[] list; if(args.length == 0) list = path.list(); else list = path.list(filter(args[0])); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } catch(Exception e) { e.printStackTrace(); } } } ///:~
注意filter()的自變量必須是final。這一點是匿名內部類要求的,使其能使用來自本身作用域以外的一個對象。
之所以認為這樣做更好,是由於FilenameFilter類現在同DirList2緊密地結合在一起。然而,我們可采取進一步的操作,將匿名內部類定義成list()的一個參數,使其顯得更加精簡。如下所示:
//: DirList3.java // Building the anonymous inner class "in-place" import java.io.*; public class DirList3 { public static void main(final String[] args) { try { File path = new File("."); String[] list; if(args.length == 0) list = path.list(); else list = path.list( new FilenameFilter() { public boolean accept(File dir, String n) { String f = new File(n).getName(); return f.indexOf(args[0]) != -1; } }); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } catch(Exception e) { e.printStackTrace(); } } } ///:~
main()現在的自變量是final,因為匿名內部類直接使用args[0]。
這展示了如何利用匿名內部類快速創建精簡的類,以便解決一些復雜的問題。由於Java中的所有東西都與類有關,所以它無疑是一種相當有用的編碼技術。它的一個好處是將特定的問題隔離在一個地方統一解決。但在另一方面,這樣生成的代碼不是十分容易閱讀,所以使用時必須慎重。
2. 順序目錄列表
經常都需要文件名以排好序的方式提供。由於Java 1.0和Java 1.1都沒有提供對排序的支持(從Java 1.2開始提供),所以必須用第8章創建的SortVector將這一能力直接加入自己的程序。就象下面這樣:
//: SortedDirList.java // Displays sorted directory listing import java.io.*; import c08.*; public class SortedDirList { private File path; private String[] list; public SortedDirList(final String afn) { path = new File("."); if(afn == null) list = path.list(); else list = path.list( new FilenameFilter() { public boolean accept(File dir, String n) { String f = new File(n).getName(); return f.indexOf(afn) != -1; } }); sort(); } void print() { for(int i = 0; i < list.length; i++) System.out.println(list[i]); } private void sort() { StrSortVector sv = new StrSortVector(); for(int i = 0; i < list.length; i++) sv.addElement(list[i]); // The first time an element is pulled from // the StrSortVector the list is sorted: for(int i = 0; i < list.length; i++) list[i] = sv.elementAt(i); } // Test it: public static void main(String[] args) { SortedDirList sd; if(args.length == 0) sd = new SortedDirList(null); else sd = new SortedDirList(args[0]); sd.print(); } } ///:~
這裡進行了另外少許改進。不再是將path(路徑)和list(列表)創建為main()的本地變量,它們變成了類的成員,使它們的值能在對象“生存”期間方便地訪問。事實上,main()現在只是對類進行測試的一種方式。大家可以看到,一旦列表創建完畢,類的構建器就會自動開始對列表進行排序。
這種排序不要求區分大小寫,所以最終不會得到一組全部單詞都以大寫字母開頭的列表,跟著是全部以小寫字母開頭的列表。然而,我們注意到在以相同字母開頭的一組文件名中,大寫字母是排在前面的——這對標准的排序來說仍是一種不合格的行為。Java 1.2已成功解決了這個問題。