程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 構建器

構建器

編輯:關於JAVA

為違例編寫代碼時,我們經常要解決的一個問題是:“一旦產生違例,會正確地進行清除嗎?”大多數時候都會非常安全,但在構建器中卻是一個大問題。構建器將對象置於一個安全的起始狀態,但它可能執行一些操作——如打開一個文件。除非用戶完成對象的使用,並調用一個特殊的清除方法,否則那些操作不會得到正確的清除。若從一個構建器內部“擲”出一個違例,這些清除行為也可能不會正確地發生。所有這些都意味著在編寫構建器時,我們必須特別加以留意。
由於前面剛學了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的編程具有很高的集成度,這主要是由於編譯器會強制它們。只有知道了如何操作那些違例,才可更進一步地掌握編譯器的知識。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved