java 构造器(constructor)
有一点很重要,即你要时刻询问子句"如果异常发生了,所有东西能被正确清理码?",尽管大多数情况下时非常安全的,但涉及到构造器时,问题出现了,构造器会把对象设置成安全的初始状态,但还会又别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理,如果在构造器内抛出了异常,这些行为也许就不能正常工作了,这意味着在编写构造器时要格外细心.
用finally也许可以解决问起,但问题并非如此简单,因为finally会每次都执行清理操作,如果构造器在执行过程中半途而废,也许该对象的某部分还没有创建成功就要被清理,这又会抛出新的异常(.close()也会抛出异常)
1.构造器抛出异常要格外注意清理方法是否有必要调用,如果方法恰当,直接向上层抛出的确能简化编程
package exceptions; //: exceptions/InputFile.java // Paying attention to exceptions in constructors. import java.io.*; public class InputFile { private BufferedReader in; public 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()也可能抛出异常,所有要放到try块里面 in.close(); } catch(IOException e2) { System.out.println("in.close() unsuccessful"); } throw e; // Rethrow } finally { //由于finally总会被执行,如果在这里关闭文件则文件刚打开还没开始使用就关闭了 // Don't close it here!!! } } public String getLine() { String s; try { s = in.readLine(); } catch(IOException e) { //这这异常已被捕获,因此getLine不会抛出任何异常 throw new RuntimeException("readLine() failed");//重新抛出新的异常到上层环境,有时会简化编程 } return s; } public void dispose() { try { in.close(); System.out.println("dispose() successful"); } catch(IOException e2) { throw new RuntimeException("in.close() failed"); } } } ///:~
2.对于在构造器阶段可能抛出的异常,并且要求清理的,最安全的使用方法时使用嵌套的try子句,
package exceptions; //: exceptions/Cleanup.java // Guaranteeing proper cleanup of a resource. public class Cleanup { public static void main(String[] args) { try { InputFile in = new InputFile("Cleanup.java"); try { String s; int i = 1; while((s = in.getLine()) != null) ; // Perform line-by-line processing here... } catch(Exception e) {//这里捕捉的时getLine()方法重新抛出的异常 System.out.println("Caught Exception in main"); e.printStackTrace(System.out); } finally { //如果构造成功,则一定会执行in.dispose()清理 in.dispose(); } } catch(Exception e) { //InputFile对象在自己的try语句块优先,因此构造失败会进入这里,而不会执行内部的try块的in.colse() System.out.println("InputFile construction failed"); } } } /* Output: dispose() successful *///:~
3. 这种通用的清理惯用法在构造器不跑出任何异常时也应该应用,其基本规则时:在需要清理的对象之后,立即进入一个try-finally语句块.
//基本上,你应该仔细考虑所有的可能细节,例如本例的dispose()如果可以抛出异常,那么就需要额外的try语句块
package exceptions; //: exceptions/CleanupIdiom.java // Each disposable object must be followed by a try-finally class NeedsCleanup { // Construction can't fail private static long counter = 1; private final long id = counter++; public void dispose() { System.out.println("NeedsCleanup " + id + " disposed"); } } class ConstructionException extends Exception {} class NeedsCleanup2 extends NeedsCleanup { // Construction can fail: public NeedsCleanup2() throws ConstructionException {} } public class CleanupIdiom { public static void main(String[] args) { // Section 1: NeedsCleanup nc1 = new NeedsCleanup(); try { // ... } finally { nc1.dispose(); } // Section 2: // If construction cannot fail you can group objects: // nc5 constructor 如果对象构造不能失败就不需要任何catch //不能失败的对象构造器对象可以群众在一起 NeedsCleanup nc2 = new NeedsCleanup(); NeedsCleanup nc3 = new NeedsCleanup(); try { // ... } finally { nc3.dispose(); // Reverse order of construction nc2.dispose(); } // Section 3: // If construction can fail you must guard each one: try { NeedsCleanup2 nc4 = new NeedsCleanup2(); try { NeedsCleanup2 nc5 = new NeedsCleanup2(); try { //如果nc5对象构造失败则会调用try块清理,否则永不调用 // ... } finally { nc5.dispose(); } } catch(ConstructionException e) { System.out.println(e); } finally { nc4.dispose(); } } catch(ConstructionException e) { // nc4 constructor System.out.println(e); } } } /* Output: NeedsCleanup 1 disposed NeedsCleanup 3 disposed NeedsCleanup 2 disposed NeedsCleanup 5 disposed NeedsCleanup 4 disposed *///:~