1.异常的限制
当覆盖方法的时候,仅仅能抛出在基类方法的异常说明里列出的那些异常。
这意味着,当基类使用的代码应用到其派生类对象的时候,一样能够工资,异常也不例外。
以下的样例是在编译时施加在异常上面的限制:
public class BaseBallException extends Exception {}
public class Foul extends BaseBallException{}
public class Strike extends BaseBallException{}
public abstract class Inning {
public Inning() throws BaseBallException{}
public void event() throws BaseBallException{}
public abstract void addBat()throws Strike,Foul;
public void walk() {}
}
public class StormException extends Exception{}
public class RainedOut extends StormException{}
public class PopFoul extends Foul{}
public interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
public class StromMyInnerings extends Inning implements Storm{
/**
* @throws BaseBallException
*/
public StromMyInnerings() throws RainedOut,BaseBallException {}
public StromMyInnerings(String s)throws RainedOut,BaseBallException{}
/**
* @throws RainedOut
*/
@Override
public void rainHard() throws RainedOut {}
/**
* @throws Strike
*/
@Override
public void addBat() throws PopFoul {}
public void event(){}
public static void main(String[] args) {
StromMyInnerings stromMyInnerings;
try {
stromMyInnerings = new StromMyInnerings();
stromMyInnerings.addBat();
} catch (RainedOut e) {
System.out.println("RainedOut");
} catch (PopFoul e) {
System.out.println("PopFoul");
} catch (BaseBallException e) {
System.out.println("BaseBallException");
}
Inning inning;
try {
inning = new StromMyInnerings();
inning.addBat();
} catch (Strike e) {
System.out.println("Strike");
} catch (Foul e) {
System.out.println("Foul");
} catch (RainedOut e) {
System.out.println("RainedOut");
} catch (BaseBallException e) {
System.out.println("BaseBallException");
}
}
}
在Inning类中,能够看到构造器和event()方法都声明将抛出异常,但实际上没有抛出。
这样的方式使你能强制用户去捕获可能在覆盖后的event()版本号中添加的异常,所以它非常合理。这对于抽象方法相同成立,如addBat()。
接口Storm值得注意,由于它包括了一个在Inning中定义的方法event()和一个不在Inning中定义的方法rainHard()。
这两个方法都抛出新的异常RainedOut。
假设StromMyInnerings类在扩展Inning类的同事又出现了Storm接口,那么Storm里的event()方法就不能改变Inning中的event()方法的异常接口。
否则的话,在使用基类的时候就不能推断是否捕获了正常的异常。当然,假设接口里定义的方法不是来自于基类。比方rainHard(),那么此方法抛出什么样的异常都没有问题。
异常限制对构造器不起作用,你会发现StromMyInnerings的构造器能够抛出不论什么异常。而不必理会基类构造器所抛出的异常。
派生类构造器不能捕获基类构造器抛出的异常。
虽然在继承过程中,编译器会对异常说明做强制要求。但异常说明本身并不属于方法类型的一部分。方法类型是由方法的名字与參数的类型组成的。因此。不能基于异常说明来重载方法。
此外。一个出如今基类方法的异常。不一定会出如今派生类方法的异常说明里。换句话说,在继承和覆盖的过程中。某个特定方法的“异常说明的接口”不是变大了而是变小了–这恰好和类接口在继承时的情形相反。
2.构造器
假设一个类继承了某个类同一时候又实现了某个接口,他们有相同的接口方法,但都抛出了不同的捕获性异常,则该子类实现与重写该方法时,则方法声明处不能抛出不论什么捕获性异常了。
假设调用的父类构造器抛出捕获性异常。则子类对应的构造器也仅仅能抛出。不能在构造器里进行捕获。构造器抛出异常时正确的清理方式:比方在构造器中打开了一个文件,清理动作仅仅有在对象使用完毕而且用户调用了特殊的清理方法之后才干得以清理,而不能直接在构造器里的finally块上关闭,由于finally块是无论是否有异常都会关闭,而构造器执行成功能外界须要这个文件流。
但假设在文件成功打开后才抛出异常,则须要关闭文件,并向外界抛出异常信息:
public class InputFile {
private BufferedReader in;
public InputFile(String fname) throws Exception{
try {
in = new BufferedReader(new FileReader(fname));
} catch (FileNotFoundException e) {
System.out.println("could not open "+fname);
throw e;
}catch (Exception e) {
try {
in.close();
} catch (IOException e1) {
System.out.println("in.close() unsuccessful");
throw e;
}finally{
}
}
}
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 e) {
throw new RuntimeException("in.close() failed");
}
}
public class CleanUp {
public static void main(String[] args) {
try {
InputFile inputFile = new InputFile("D:/workspace/testjava/src/com/test/CleanUp.java");
try {
String s;
int i = 1;
while ((s = inputFile.getLine()) != null) {
}
} catch (Exception e) {
System.out.println("caught Exception in main");
e.printStackTrace(System.out);
}finally{
inputFile.dispose();
}
} catch (Exception e) {
System.out.println("InputFile construction failed");
}
}
}
InputFile 的构造器接受字符串作为參数。该字符串表示所要打开的文件名称。
在try块中,会使用此文件名称建立了FileReader对象。FileReader对象本身用处并不大,但能够用它来建立BufferedReader对象。注意。使用InputFile 的优点就是把两步操作合二为一。这样的通用的清理习惯使用方法在构造器不抛出不论什么异常时也应该运用,其基本规则是:在创建须要清理的对象之后,马上进入一个try-finally语句块。
3.异常匹配
抛出异常的时候。异常处理系统会依照代码的书写顺序找出“近期”的处理程序。找到匹配的处理程序之后,它就觉得异常将得到处理,然后就不再继续查找。
查找的时候并不要求抛出的异常处理程序所声明的异常全然匹配。
派生类的对象也能够匹配其基类的处理程序,样例例如以下:
public class Annoyance extends Exception{}
public class Sneeze extends Annoyance{}
public class Human {
public static void main(String[] args) {
try {
throw new Sneeze();
} catch (Sneeze e) {
System.out.println("catch Sneeze");
}catch (Annoyance e) {
System.out.println("catch Annoyance");
}
try {
throw new Sneeze();
}catch (Annoyance e) {
System.out.println("catch Annoyance exception");
}
}
}
Sneeze异常会被第一个匹配的catch自居补货。也就是程序里的第一个。
然而假设将这个catch字句删掉,仅仅留下Annoyance的catch子句,该横向仍然能正常执行,由于这次补货的是Sneeze的基类。换句话说。catch(Annoyance e)会捕获Annoyance以及全部从它派生的异常。
假设把捕获基类的catch子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样:
try {
throw new Sneeze();
}catch (Annoyance e) {
//...
} catch (Sneeze e) {
//.....
}
这样编译器就会发现Sneeze的catch子句永远也得不到执行。因此会报错。
4.其它的可选方式
异常处理的一个重要的原则是“仅仅有在你知道怎样处理的情况下才捕获异常”。实际上,异常处理的一个重要目标就是把错误处理的代码同发生错误的地点相分离。
这使你能在一段代码中专注于要完毕的事情。至于怎样处理错误,则放在还有一段代码中。
这样以来,主干代码就不会与错误处理逻辑混在一起,也更easy理解和维护。
“被检查的异常”可能使问题变得复杂,由于它们强制你在可能还没有准备优点理错误的时候*加上cacth子名,即使我们不知道怎样处理的情况下,这就导致了异常的隐藏:
try{
//… throw …
}catch(Exception e){//什么都不做
}
把“被检查的异常”转换为“不检查的异常”:当在一个普通方法里调用别的方法时,要考虑到“我不知道该怎样处理这个异常,可是也不能把它‘吞’了,或者仅仅打印一些没用的消息”。
JDK1.4的异常链提供了一种新的思路来解决问题。能够直接把“被检查的异常”包装进RuntimeException里面:
try{
//… throw 检查异常…
}catch(IDontKnowWhatToDoWithThisCheckedException e){
Throw new RuntimeException(e);
}
假设想把“被检查的异常”这样的功能“屏蔽”掉的话,上面是一个好的办法。不用“吞”掉异常,也不必把它放到方法的异常声明里面。而异常链还能保证你不会丢失不论什么原始异常的信息。
你还能够在“知道怎样处理的地方”来处理它,也能够其它上层catch里通过 throw e.getCause(); 再次抛出原始的“被检查的异常”。
5.异常使用指南
应该在以下情况使用异常:
1)在恰当的级别处理问题。(在知道该怎样处理的情况下才捕获异常。
)
2)解决问题而且又一次调用产生异常的方法。
3)进行少许修补,然后绕过异常产生的地方继续执行。
4)用别的数据进行计算,以取代方法估计会返回的值。
5)把当前执行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
6)把当前执行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
7)终止程序。
8)进行简化。
9)让类库和程序更安全。
6.总结
异常时java程序设计不可切割的一部分。假设不了解 怎样使用它们,那你仅仅能完毕非常有限的工作。异常处理的有点之中的一个就是它使得你能够在某处集中精力处理你要解决的问题,而在还有一边处理你编写的这段代码中产生的错误。