异常的丢失
Java的异常实现也有瑕疵,异常作为程序出错的的标志,绝不应该被忽略,但它还是有可能被轻易的忽略。用某些特殊的方式使用finally子句,就会发生这种情况:
package com.huangfei.thinkinginjava.exceptions;
class VeryImportantException extends Exception{
@Override
public String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception{
@Override
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) {
try {
LostMessage lm = new LostMessage();
try {
lm.f();
} finally{
lm.dispose();
}
} catch (Exception e) {
System.out.println(e);
}
}
}
//output
A trivial exception!
从输出中可以看出,VeryImportantException不见了,他被finally子句里的HoHumException所取代。这是相当严重的缺陷,因为异常可能会以一种比前面例子所示更微妙和难以察觉的方式完全丢失。
一种更简单的丢失异常的方式是从finally子句里返回:
package com.huangfei.thinkinginjava.exceptions;
public class ExceptionSilencer {
public static void main(String[] args) {
try {
throw new VeryImportantException();
} finally {
return;
}
}
}
如果运行这个程序,就会看到即使抛出了异常,它也不会产生任何输出。
异常的限制
package com.huangfei.thinkinginjava.exceptions;
class BaseballException extends Exception {
}
class Foul extends BaseballException {
}
class Strike extends BaseballException {
}
class PopFoul extends Foul {
}
abstract class Inning {
public Inning() throws BaseballException {
}
/**
* 在构造器和event()方法中声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的event()版本中增加
* 的异常,所以它很合理。这对于抽象方法同样成立,比如atBat()
*/
public void event() throws BaseballException {
}
public abstract void atBat() throws Strike, Foul;
public void walk() {
}
}
class StormException extends Exception {
}
class RainedOut extends StormException {
}
interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm {
/**
* 异常的限制对构造器不起作用,StormyInning的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。
* 然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动调用),派生类构造器的异常说明必须
* 包含基类构造器的异常说明。并且派生类构造器不能捕获基类构造器抛出的异常,比如这样是不允许的:
* try {
super();
} catch (Exception e) {
}
*
*/
public StormyInning() throws BaseballException, RainedOut {
}
public StormyInning(String s) throws Foul, BaseballException {
}
/**
* StormyInning.walk()不能通过编译的原因是因为它抛出了异常,而Inning.walk()并没有声明此异常。如果编译器允许这样做的话,
* 就可以在调用Inning.walk()的时候不用做异常处理了,而当把它替换为Inning的派生类的对象时,这个方法就有可能会抛出异常,于是
* 程序就失灵了。通过强制派生类遵守基类方法的异常说明,对象的可替代性得到了保证。
*/
// void walk() throws PopFoul {}
/**
* Storm包含了一个在Inning中定义的方法event()和一个不在Inning中定义的方法rainHard()。这两个方法都抛出新的异常RainedOut。
* 如果StormyInning类在扩展Inning类的同时又实现了Storm接口,那么Storm里的event()方法就不能改变在Inning中的event()方法
* 的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常。
*/
// public void event() throws RainedOut {}
public void rainHard() throws RainedOut{
}
/**
* event()方法的一种正确用法是在派生类中可以不抛出任何异常,即使它是基定义的异常。假使基类的方法会抛出异常,这样做也不会破坏已有的程序。
*/
public void event() {
}
/**
* atBat()抛出的PopFoul,这个异常是继承自“会被基类的atBat()抛出”的Foul。这样,如果写的代码是同Inning打交道,并且调用了
* 它的atBat()的话,那么肯定能捕获Foul。而PopFoul是由Foul派生出来的,因此异常处理程序也能捕获PopFoul。
*/
public 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");
}
//将StormyInning向上转型为Inning基类,编译器就会正确地要求你捕获基类的异常。
try {
Inning i = new StormyInning();
i.atBat();
} 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");
}
}
}