# 10.4 File類
`File`類有一個欺騙性的名字——通常會認為它對付的是一個文件,但實情并非如此。它既代表一個特定文件的名字,也代表目錄內一系列文件的名字。若代表一個文件集,便可用`list()`方法查詢這個集,返回的是一個字符串數組。之所以要返回一個數組,而非某個靈活的集合類,是因為元素的數量是固定的。而且若想得到一個不同的目錄列表,只需創建一個不同的`File`對象即可。事實上,`FilePath`(文件路徑)似乎是一個更好的名字。本節將向大家完整地例示如何使用這個類,其中包括相關的`FilenameFilter`(文件名過濾器)接口。
## 10.4.1 目錄列表器
現在假設我們想觀看一個目錄列表。可用兩種方式列出`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已成功解決了這個問題。
## 10.4.2 檢查與創建目錄
`File`類并不僅僅是對現有目錄路徑、文件或者文件組的一個表示。亦可用一個`File`對象新建一個目錄,甚至創建一個完整的目錄路徑——假如它尚不存在的話。亦可用它了解文件的屬性(長度、上一次修改日期、讀/寫屬性等),檢查一個`File`對象到底代表一個文件還是一個目錄,以及刪除一個文件等等。下列程序完整展示了如何運用`File`類剩下的這些方法:
```
//: MakeDirectories.java
// Demonstrates the use of the File class to
// create directories and manipulate files.
import java.io.*;
public class MakeDirectories {
private final static String usage =
"Usage:MakeDirectories path1 ...\n" +
"Creates each path\n" +
"Usage:MakeDirectories -d path1 ...\n" +
"Deletes each path\n" +
"Usage:MakeDirectories -r path1 path2\n" +
"Renames from path1 to path2\n";
private static void usage() {
System.err.println(usage);
System.exit(1);
}
private static void fileData(File f) {
System.out.println(
"Absolute path: " + f.getAbsolutePath() +
"\n Can read: " + f.canRead() +
"\n Can write: " + f.canWrite() +
"\n getName: " + f.getName() +
"\n getParent: " + f.getParent() +
"\n getPath: " + f.getPath() +
"\n length: " + f.length() +
"\n lastModified: " + f.lastModified());
if(f.isFile())
System.out.println("it's a file");
else if(f.isDirectory())
System.out.println("it's a directory");
}
public static void main(String[] args) {
if(args.length < 1) usage();
if(args[0].equals("-r")) {
if(args.length != 3) usage();
File
old = new File(args[1]),
rname = new File(args[2]);
old.renameTo(rname);
fileData(old);
fileData(rname);
return; // Exit main
}
int count = 0;
boolean del = false;
if(args[0].equals("-d")) {
count++;
del = true;
}
for( ; count < args.length; count++) {
File f = new File(args[count]);
if(f.exists()) {
System.out.println(f + " exists");
if(del) {
System.out.println("deleting..." + f);
f.delete();
}
}
else { // Doesn't exist
if(!del) {
f.mkdirs();
System.out.println("created " + f);
}
}
fileData(f);
}
}
} ///:~
```
在`fileData()`中,可看到應用了各種文件調查方法來顯示與文件或目錄路徑有關的信息。
`main()`應用的第一個方法是`renameTo()`,利用它可以重命名(或移動)一個文件至一個全新的路徑(該路徑由參數決定),它屬于另一個`File`對象。這也適用于任何長度的目錄。
若試驗上述程序,就可發現自己能制作任意復雜程度的一個目錄路徑,因為`mkdirs()`會幫我們完成所有工作。在Java 1.0中,`-d`標志報告目錄雖然已被刪除,但它依然存在;但在Java 1.1中,目錄會被實際刪除。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物