网站关键词优化,男科医院治疗一次2000元,中国企业公司大全,网站运营成本本章概要
异常限制构造器
异常限制
当覆盖方法的时候#xff0c;只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用#xff0c;因为这意味着与基类一起工作的代码#xff0c;也能和导出类一起正常工作#xff08;这是面向对象的基本概念#xff09;#…本章概要
异常限制构造器
异常限制
当覆盖方法的时候只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用因为这意味着与基类一起工作的代码也能和导出类一起正常工作这是面向对象的基本概念异常也不例外。
下面例子演示了这种在编译时施加在异常上面的限制
// exceptions/StormyInning.java
// Overridden methods can throw only the exceptions
// specified in their base-class versions, or exceptions
// derived from the base-class exceptions
class BaseballException extends Exception {
}class Foul extends BaseballException {
}class Strike extends BaseballException {
}abstract class Inning {Inning() throws BaseballException {}public void event() throws BaseballException {// Doesnt actually have to throw anything}public abstract void atBat() throws Strike, Foul;public void walk() {} // Throws no checked exceptions
}class StormException extends Exception {
}class RainedOut extends StormException {
}class PopFoul extends Foul {
}interface Storm {void event() throws RainedOut;void rainHard() throws RainedOut;
}public class StormyInning extends Inning implements Storm {// OK to add new exceptions for constructors, but you// must deal with the base constructor exceptions:public StormyInning()throws RainedOut, BaseballException {}public StormyInning(String s)throws BaseballException {}// Regular methods must conform to base class://- void walk() throws PopFoul {} //Compile error// Interface CANNOT add exceptions to existing// methods from the base class://- public void event() throws RainedOut {}// If the method doesnt already exist in the// base class, the exception is OK:Overridepublic void rainHard() throws RainedOut {}// You can choose to not throw any exceptions,// even if the base version does:Overridepublic void event() {}// Overridden methods can throw inherited exceptions:Overridepublic void atBat() throws PopFoul {}public static void main(String[] args) {try {StormyInning si new StormyInning();si.atBat();} catch (PopFoul e) {System.out.println(Pop foul);} catch (RainedOut e) {System.out.println(Rained out);} catch (BaseballException e) {System.out.println(Generic baseball exception);}// Strike not thrown in derived version.try {// What happens if you upcast?Inning i new StormyInning();i.atBat();// You must catch the exceptions from the// base-class version of the method:} catch (Strike e) {System.out.println(Strike);} catch (Foul e) {System.out.println(Foul);} catch (RainedOut e) {System.out.println(Rained out);} catch (BaseballException e) {System.out.println(Generic baseball exception);}}
}在 Inning 类中可以看到构造器和 event() 方法都声明将抛出异常但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常所以它很合理。这对于抽象方法同样成立比如 atBat()。
接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event() 方法的异常接口。否则的话在使用基类的时候就不能判断是否捕获了正确的异常所以这也很合理。当然如果接口里定义的方法不是来自于基类比如 rainHard()那么此方法抛出什么样的异常都没有问题。
异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常而不必理会基类构造器所抛出的异常。然而因为基类构造器必须以这样或那样的方式被调用这里默认构造器将自动被调用派生类构造器的异常说明必须包含基类构造器的异常说明。
派生类构造器不能捕获基类构造器抛出的异常。
StormyInning.walk() 不能通过编译是因为它抛出了一个 Inning.walk() 中没有声明的异常。如果编译器允许这么做的话就可以编写调用Inning.walk()却不处理任何异常的代码。 但是当使用 Inning派生类的对象时就会抛出异常从而导致程序出现问题。通过强制派生类遵守基类方法的异常说明对象的可替换性得到了保证。
覆盖后的 event() 方法表明派生类版的方法可以不抛出任何异常即使基类版的方法抛出了异常。因为这样做不会破坏那些假定基类版的方法会抛出异常的代码。类似的情况出现在 atBat()上它抛出的异常PopFoul是由基类版atBat()抛出的Foul 异常派生而来。如果你写的代码同 Inning 一起工作并且调用了 atBat()的话那么肯定能捕获 Foul 。又因为 PopFoul 是由 Foul派生而来因此异常处理程序也能捕获 PopFoul。
最后一个有趣的地方在 main()。如果处理的刚好是 Stormylnning 对象的话编译器只要求捕获这个类所抛出的异常。但是如果将它向上转型成基类型那么编译器就会准确地要求捕获基类的异常。所有这些限制都是为了能产生更为健壮的异常处理代码。
尽管在继承过程中编译器会对异常说明做强制要求但异常说明本身并不属于方法类型的一部分方法类型是由方法的名字与参数的类型组成的。因此不能基于异常说明来重载方法。此外一个出现在基类方法的异常说明中的异常不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同在继承中基类的方法必须出现在派生类里换句话说在继承和覆盖的过程中某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。
构造器
有一点很重要即你要时刻询问自己“如果异常发生了所有东西能被正确的清理吗尽管大多数情况下是非常安全的但涉及构造器时问题就出现了。构造器会把对象设置成安全的初始状态但还会有别的动作比如打开一个文件这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。
你也许会认为使用 finally 就可以解决问题。但问题并非如此简单因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废也许该对象的某些部分还没有被成功创建而这些部分在 finally 子句中却是要被清理的。
在下面的例子中建立了一个 InputFile 类它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类这些类的基本用法很简单你应该很容易明白
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);// Wasnt open, so dont close itthrow e;} catch (Exception e) {// All other exceptions must close ittry {in.close();} catch (IOException e2) {System.out.println(in.close() unsuccessful);}throw e; // Rethrow} finally {// Dont close it here!!!}}public String getLine() {String s;try {s in.readLine();} catch (IOException e) {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);}}
}InputFile 的构造器接受字符串作为参数该字符串表示所要打开的文件名。在 try 块中会使用此文件名建立 FileReader 对象。FileReader 对象本身用处并不大但可以用它来建立 BufferedReader 对象。注意使用 InputFile 的好处之一是把两步操作合而为一。
如果 FileReader 的构造器失败了将抛出 FileNotFoundException 异常。对于这个异常并不需要关闭文件因为这个文件还没有被打开。而任何其他捕获异常的 catch 子句必须关闭文件因为在它们捕获到异常之时文件已经打开了当然如果还有其他方法能抛出 FileNotFoundException这个方法就显得有些投机取巧了。这时通常必须把这些方法分别放到各自的 try 块里close() 方法也可能会抛出异常所以尽管它已经在另一个 catch 子句块里了还是要再用一层 try-catch这对 Java 编译器而言只不过是多了一对花括号。在本地做完处理之后异常被重新抛出对于构造器而言这么做是很合适的因为你总不希望去误导调用方让他认为“这个对象已经创建完毕可以使用了”。
在本例中由于 finally 会在每次完成构造器之后都执行一遍因此它实在不该是调用 close() 关闭文件的地方。我们希望文件在 InputFlle 对象的整个生命周期内都处于打开状态。
getLine() 方法会返回表示文件下一行内容的字符串。它调用了能抛出异常的 readLine()但是这个异常已经在方法内得到处理因此 getLine() 不会抛出任何异常。在设计异常时有一个问题应该把异常全部放在这一层处理还是先处理一部分然后再向上层抛出相同的或新的异常又或者是不做任何处理直接向上层抛出。如果用法恰当的话直接向上层抛出的确能简化编程。在这里getLine() 方法将异常转换为 RuntimeException表示一个编程错误。
用户在不再需要 InputFile 对象时就必须调用 dispose() 方法这将释放 BufferedReader 和/或 FileReader 对象所占用的系统资源比如文件句柄在使用完 InputFile 对象之前是不会调用它的。可能你会考虑把上述功能放到 finalize() 里面但我在 封装 讲过你不知道 finalize() 会不会被调用即使能确定它将被调用也不知道在什么时候调用这也是 Java 的缺陷除了内存的清理之外所有的清理都不会自动发生。所以必须告诉客户端程序员这是他们的责任。
对于在构造阶段可能会抛出异常并且要求清理的类最安全的使用方式是使用嵌套的 try 子句
// exceptions/Cleanup.java
// Guaranteeing proper cleanup of a resource
public class Cleanup {public static void main(String[] args) {try {InputFile in new InputFile(D:\\onJava\\myTest\\base\\Cleanup.java);try {String s;int i 1;while ((s in.getLine()) ! null) {// Perform line-by-line processing here...}} catch (Exception e) {System.out.println(Caught Exception in main);e.printStackTrace(System.out);} finally {in.dispose();}} catch (Exception e) {System.out.println(InputFile construction failed);}}
}输出为 请仔细观察这里的逻辑对 InputFile 对象的构造在其自己的 try 语句块中有效如果构造失败将进入外部的 catch 子句而 dispose() 方法不会被调用。但是如果构造成功我们肯定想确保对象能够被清理因此在构造之后立即创建了一个新的 try 语句块。执行清理的 finally 与内部的 try 语句块相关联。在这种方式中finally 子句在构造失败时是不会执行的而在构造成功时将总是执行。
这种通用的清理惯用法在构造器不抛出任何异常时也应该运用其基本规则是在创建需要清理的对象之后立即进入一个 try-finally 语句块
// exceptions/CleanupIdiom.java
// Disposable objects must be followed by a try-finally
class NeedsCleanup { // Construction cant failprivate 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:NeedsCleanup2() throws ConstructionException {}
}public class CleanupIdiom {public static void main(String[] args) {// [1]:NeedsCleanup nc1 new NeedsCleanup();try {// ...} finally {nc1.dispose();}// [2]:// If construction cannot fail,// you can group objects:NeedsCleanup nc2 new NeedsCleanup();NeedsCleanup nc3 new NeedsCleanup();try {// ...} finally {nc3.dispose(); // Reverse order of constructionnc2.dispose();}// [3]:// If construction can fail you must guard each one:try {NeedsCleanup2 nc4 new NeedsCleanup2();try {NeedsCleanup2 nc5 new NeedsCleanup2();try {// ...} finally {nc5.dispose();}} catch (ConstructionException e) { // nc5 const.System.out.println(e);} finally {nc4.dispose();}} catch (ConstructionException e) { // nc4 const.System.out.println(e);}}
}输出为 [1] 相当简单遵循了在可去除对象之后紧跟 try-finally 的原则。如果对象构造不会失败就不需要任何 catch。[2] 为了构造和清理可以看到将具有不能失败的构造器的对象分组在一起。[3] 展示了如何处理那些具有可以失败的构造器且需要清理的对象。为了正确处理这种情况事情变得很棘手因为对于每一个构造都必须包含在其自己的 try-finally 语句块中并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。
本例中异常处理的混乱情形有力的论证了应该创建不会抛出异常的构造器尽管这并不总会实现。
注意如果 dispose() 可以抛出异常那么你可能需要额外的 try 语句块。基本上你应该仔细考虑所有的可能性并确保正确处理每一种情况。