# 9.7 構造器
為異常編寫代碼時,我們經常要解決的一個問題是:“一旦產生異常,會正確地進行清除嗎?”大多數時候都會非常安全,但在構造器中卻是一個大問題。構造器將對象置于一個安全的起始狀態,但它可能執行一些操作——如打開一個文件。除非用戶完成對象的使用,并調用一個特殊的清除方法,否則那些操作不會得到正確的清除。若從一個構造器內部“拋”出一個異常,這些清除行為也可能不會正確地發生。所有這些都意味著在編寫構造器時,我們必須特別加以留意。
由于前面剛學了`finally`,所以大家可能認為它是一種合適的方案。但事情并沒有這么簡單,因為`finally`每次都會執行清除代碼——即使我們在清除方法運行之前不想執行清除代碼。因此,假如真的用`finally`進行清除,必須在構造器正常結束時設置某種形式的標志。而且只要設置了標志,就不要執行`finally`塊內的任何東西。由于這種做法并不完美(需要將一個地方的代碼同另一個地方的結合起來),所以除非特別需要,否則一般不要嘗試在`finally`中進行這種形式的清除。
在下面這個例子里,我們創建了一個名為`InputFile`的類。它的作用是打開一個文件,然后每次讀取它的一行內容(轉換為一個字符串)。它利用了由Java標準IO庫提供的`FileReader`以及`BufferedReader`類(將于第10章討論)。這兩個類都非常簡單,大家現在可以毫無困難地掌握它們的基本用法:
```
//: Cleanup.java
// Paying attention to exceptions
// in constructors
import java.io.*;
class InputFile {
private BufferedReader in;
InputFile(String fname) throws Exception {
try {
in =
new BufferedReader(
new FileReader(fname));
// Other code that might throw exceptions
} catch(FileNotFoundException e) {
System.out.println(
"Could not open " + fname);
// Wasn't open, so don't close it
throw e;
} catch(Exception e) {
// All other exceptions must close it
try {
in.close();
} catch(IOException e2) {
System.out.println(
"in.close() unsuccessful");
}
throw e;
} finally {
// Don't close it here!!!
}
}
String getLine() {
String s;
try {
s = in.readLine();
} catch(IOException e) {
System.out.println(
"readLine() unsuccessful");
s = "failed";
}
return s;
}
void cleanup() {
try {
in.close();
} catch(IOException e2) {
System.out.println(
"in.close() unsuccessful");
}
}
}
public class Cleanup {
public static void main(String[] args) {
try {
InputFile in =
new InputFile("Cleanup.java");
String s;
int i = 1;
while((s = in.getLine()) != null)
System.out.println(""+ i++ + ": " + s);
in.cleanup();
} catch(Exception e) {
System.out.println(
"Caught in main, e.printStackTrace()");
e.printStackTrace();
}
}
} ///:~
```
該例使用了Java 1.1 IO類。
用于`InputFile`的構造器采用了一個`String`(字符串)參數,它代表我們想打開的那個文件的名字。在一個`try`塊內部,它用該文件名創建了一個`FileReader`。對`FileReader`來說,除非轉移并用它創建一個能夠實際與之“交談”的`BufferedReader`,否則便沒什么用處。注意`InputFile`的一個好處就是它同時合并了這兩種行動。
若`FileReader`構造器不成功,就會產生一個`FileNotFoundException`(文件未找到異常)。必須單獨捕獲這個異常——這屬于我們不想關閉文件的一種特殊情況,因為文件尚未成功打開。其他任何捕獲從句(`catch`)都必須關閉文件,因為文件已在進入那些捕獲從句時打開(當然,如果多個方法都能產生一個`FileNotFoundException`異常,就需要稍微用一些技巧。此時,我們可將不同的情況分隔到數個`try`塊內)。`close()`方法會拋出一個嘗試過的異常。即使它在另一個`catch`從句的代碼塊內,該異常也會得以捕獲——對Java編譯器來說,那個`catch`從句不過是另一對花括號而已。執行完本地操作后,異常會被重新“拋”出。這樣做是必要的,因為這個構造器的執行已經失敗,我們不希望調用方法來假設對象已正確創建以及有效。
在這個例子中,沒有采用前述的標志技術,`finally`從句顯然不是關閉文件的正確地方,因為這可能在每次構造器結束的時候關閉它。由于我們希望文件在`InputFile`對象處于活動狀態時一直保持打開狀態,所以這樣做并不恰當。
`getLine()`方法會返回一個字符串,其中包含了文件中下一行的內容。它調用了`readLine()`,后者可能產生一個異常,但那個異常會被捕獲,使`getLine()`不會再產生任何異常。對異常來說,一項特別的設計問題是決定在這一級完全控制一個異常,還是進行部分控制,并傳遞相同(或不同)的異常,或者只是簡單地傳遞它。在適當的時候,簡單地傳遞可極大簡化我們的編碼工作。
`getLine()`方法會變成:
```
String getLine() throws IOException {
return in.readLine();
}
```
但是當然,調用者現在需要對可能產生的任何`IOException`進行控制。
用戶使用完畢`InputFile`對象后,必須調用`cleanup()`方法,以便釋放由`BufferedReader`以及/或者`FileReader`占用的系統資源(如文件引用)——注釋⑥。除非`InputFile`對象使用完畢,而且到了需要棄之不用的時候,否則不應進行清除。大家可能想把這樣的機制置入一個`finalize()`方法內,但正如第4章指出的那樣,并非總能保證`finalize()`獲得正確的調用(即便確定它會調用,也不知道何時開始)。這屬于Java的一項缺陷——除內存清除之外的所有清除都不會自動進行,所以必須知會客戶程序員,告訴他們有責任用`finalize()`保證清除工作的正確進行。
⑥:在C++里,“析構器”可幫我們控制這一局面。
在`Cleanup.java`中,我們創建了一個`InputFile`,用它打開用于創建程序的相同的源文件。同時一次讀取該文件的一行內容,而且添加相應的行號。所有異常都會在`main()`中被捕獲——盡管我們可選擇更大的可靠性。
這個示例也向大家展示了為何在本書的這個地方引入異常的概念。異常與Java的編程具有很高的集成度,這主要是由于編譯器會強制它們。只有知道了如何操作那些異常,才可更進一步地掌握編譯器的知識。
- 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 推薦讀物