八宝书库 > 文学其他电子书 > Java编程思想第4版[中文版](PDF格式) >

第77部分

Java编程思想第4版[中文版](PDF格式)-第77部分

小说: Java编程思想第4版[中文版](PDF格式) 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




      System。out。println(  

        〃Caught Ex in first try block〃);  

    } finally {  

      System。out。println(  

        〃finally in 1st try block〃);  

    }  

  }  

} ///:~  

  

该程序的输出展示了具体发生的事情:  

  

Entering first try block  

Entering second try block  

finally in 2nd try block  

Caught Ex in first try block  

finally in 1st try block  

  

若调用了break 和 continue 语句,finally 语句也会得以执行。请注意,与作上标签的break 和 continue 

一道,finally排除了 Java 对 goto 跳转语句的需求。  



9。6。2  缺点:丢失的违例  



一般情况下,Java 的违例实施方案都显得十分出色。不幸的是,它依然存在一个缺点。尽管违例指出程序里 

存在一个危机,而且绝不应忽略,但一个违例仍有可能简单地“丢失”。在采用 finally 从句的一种特殊配 

置下,便有可能发生这种情况:  

  

//: LostMessage。java  

// How an exception can be lost  

  

class VeryImportantException extends Exception {  

  public String toString() {  

    return 〃A very important exception!〃;  

  }  



                                                                                          279 


…………………………………………………………Page 281……………………………………………………………

}  

  

class HoHumException extends Exception {  

  public String toString() {  

    return 〃A trivial exception〃;  

  }  

}  

  

public class LostMessage {  

  void f() throws VeryImportantException {  

    throw new VeryImportantException();  

  }  

  void dispose() throws HoHumException {  

    throw new HoHumException();  

  }  

  public static void main(String'' args)   

      throws Exception {  

    LostMessage lm = new LostMessage();  

    try {  

      lm。f();  

    } finally {  

      lm。dispose();  

    }  

  }  

} ///:~  

  

输出如下:  

  

A trivial exception  

        at LostMessage。dispose(LostMessage。java:21)  

        at LostMessage。main(LostMessage。java:29)  

  

可以看到,这里不存在 VeryImportantException (非常重要的违例)的迹象,它只是简单地被finally从句 

中的HoHumException 代替了。  

这是一项相当严重的缺陷,因为它意味着一个违例可能完全丢失。而且就象前例演示的那样,这种丢失显得 

非常“自然”,很难被人查出蛛丝马迹。而与此相反,C++里如果第二个违例在第一个违例得到控制前产生, 

就会被当作一个严重的编程错误处理。或许Java 以后的版本会纠正这个问题(上述结果是用Java 1。1 生成 

的)。  



9。7 构建器  



为违例编写代码时,我们经常要解决的一个问题是:“一旦产生违例,会正确地进行清除吗?”大多数时候 

都会非常安全,但在构建器中却是一个大问题。构建器将对象置于一个安全的起始状态,但它可能执行一些 

操作——如打开一个文件。除非用户完成对象的使用,并调用一个特殊的清除方法,否则那些操作不会得到 

正确的清除。若从一个构建器内部“掷”出一个违例,这些清除行为也可能不会正确地发生。所有这些都意 

味着在编写构建器时,我们必须特别加以留意。  

由于前面刚学了finally,所以大家可能认为它是一种合适的方案。但事情并没有这么简单,因为 finally 

每次都会执行清除代码——即使我们在清除方法运行之前不想执行清除代码。因此,假如真的用 finally进 

行清除,必须在构建器正常结束时设置某种形式的标志。而且只要设置了标志,就不要执行 finally 块内的 

任何东西。由于这种做法并不完美 (需要将一个地方的代码同另一个地方的结合起来),所以除非特别需 

要,否则一般不要尝试在finally 中进行这种形式的清除。  

在下面这个例子里,我们创建了一个名为 InputFile 的类。它的作用是打开一个文件,然后每次读取它的一 

行内容(转换为一个字串)。它利用了由 Java 标准 IO 库提供的FileReader 以及BufferedReader 类(将于 



                                                                                        280 


…………………………………………………………Page 282……………………………………………………………

第 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〃);  

    }  

  }  



                                                                                          281 


…………………………………………………………Page 283……………………………………………………………

}  

  

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++里,“破坏器”可帮我们控制这一局面。  



                                                                                   282 


…………………………………………………………Page 284……………………………………………………………

  

在Cleanup。java 中,我们创建了一个InputFile,用它打开用于创建程序的相同的源文件。同时一次读取该 

文件的一行内容,而且添加相应的行号。所有违例都会在 main()中被捕获——尽管我们可选择更大的可靠 

性。  

这个示例也向大家展示了为何在本书的这个地方引入违例的概念。违例与 Java 的编程具有很高的集成度,这 

主要是由于编译器会强制它们。只有知道了如何操作那些违例,才可更进一步地掌握编译器的知识。  



9。8 违例匹配  



 “掷”出一个违例后,违例控制系统会按当初编写的顺序搜索“最接近”的控制器。一旦找到相符的控制 

器,就认为违例已得到控制,不再进行更多的搜索工作。  

在违例和它的控制器之间,并不需要非常精确的匹配。一个衍生类对象可与基础类的一个控制器相配,如下 

例所示:  

  

//: Human。java  

// Catching Exception Hierarchies  

  

class Annoyance extends Exception {}  

class Sneeze extends Annoyance {}  

  

public class Human {  

  public static void main(String '' args) {  

    try {  

      throw new Sneeze();  

    } catch(Sneeze s) {  

      System。out。println(〃Caught Sneeze〃);  

    } catch(Annoyance a) {  

      System。out。println(〃Caught Annoyance〃);  

    }  

  }  

} ///:~  

  

Sneeze 违例会被相符的第一个 catch 从句捕获。当然,这只是第一个。然而,假如我们删除第一个 catch 从 

句:  

  

    try {  

      throw new Sneeze();  

    } catch(Annoyance a) {  

      System。out。println(〃Caught Annoyance〃);  

    }  

  

那么剩下的 catch 从句依然能够工作,因为它捕获的是 Sneeze 的基础类。换言之,catch(Annoyance e)能捕 

获一个Annoyance 以及从它衍生的任何类。这一点非常重要,因为一旦我们决定为一个方法添加更多的违 

例,而且它们都是从相同的基础类继承的,那么客户程序员的代码就不需要更改。至少能够假定它们捕获的 

是基础类。  

若将基础类捕获从句置于第一位,试图“屏蔽”衍生类违例,就象下面这样:  

  

    try {  

      throw new Sneeze();  

    } catch(Annoyance a) {  

      System。out。println(〃Ca

返回目录 上一页 下一页 回到顶部 0 1

你可能喜欢的