多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 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的编程具有很高的集成度,这主要是由于编译器会强制它们。只有知道了如何操作那些异常,才可更进一步地掌握编译器的知识。