9.7 构建器

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的编程具有很高的集成度,这主要是由于编译器会强制它们。只有知道了如何操作那些违例,才可更进一步地掌握编译器的知识。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,198评论 6 514
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,334评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,643评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,495评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,502评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,156评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,743评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,659评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,200评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,282评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,424评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,107评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,789评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,264评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,390评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,798评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,435评论 2 359

推荐阅读更多精彩内容

  • 9.2 违例的捕获 若某个方法产生一个违例,必须保证该违例能被捕获,并获得正确对待。对于Java的违例控制机制,它...
    5e0g阅读 305评论 0 2
  • 9.6 用finally清除 无论一个违例是否在try块中发生,我们经常都想执行一些特定的代码。对一些特定的操作,...
    5e0g阅读 154评论 0 2
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,333评论 0 9
  • JAVA面试题 1、作用域public,private,protected,以及不写时的区别答:区别如下:作用域 ...
    JA尐白阅读 1,157评论 1 0
  • 7.7 构建器和多形性 同往常一样,构建器与其他种类的方法是有区别的。在涉及到多形性的问题后,这种方法依然成立。尽...
    5e0g阅读 180评论 0 2