第9章 违例差错控制
1.基本违例
方法是创建一个特殊的对象,代表我们的信息,将其掷出我们当前的场景之外,
产生一个违例:
if(t == null)
throw new NullPointerException();
这里用new在内存堆里创建违例,并需要调用一个构建器。标准的违例中,有两
个构建器,一个是默认构建器,另一个是需要使用一个字串自变量。
if(t == null)
throw new NullPointerException("t = null");
2.违例的捕获
try从句是警戒区,每个catch从句就是违例控制器
try {
// Code that might generate exceptions
} catch(Type1 id1) {
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Type3 id3) {
// Handle exceptions of Type3
}
违例控制理论中,有两种基本方法,一种是“中断”方法,另一种是“恢复“方法。
我们一般用的是”中断“方法。
3.违例规范
采用关键字throws,后面跟随全部潜在的违例类型
void f() throws tooBig, tooSmall, divZero { //...
可以掷出一个并没有发生的违例,强迫使用这个方法的用户当真的产生了那个违例
处理。在实际应用中,可将其作为那个违例的“占位符”使用。以后就可以产生实际
的违例,无需修改现有代码。
4.捕获所有违例
具体的做法是捕获基础类违例类型Exception
catch(Exception e) {
System.out.println("caught an exception");
}
实际使用的时候最好放在控制器列表的末尾,防止前面的控制器失效。
getClass()返回一个代表这个对象的类,还可以用getName()或
toString()查询这个class类的名字。
5.重新掷出违例
catch(Exception e) {
System.out.println("一个违例已经产生");
throw e;
}
printStackTrace()内的那个违例有关的信息会与违例的起源地对应,
fillInStackTrace()可安装新的堆栈跟踪信息,与违例抛出地点对应。
6.throwable
Throwable是Exception的一个基础类,所以能够掷出对象,具有Throwable
属性,但不是一个Exception违例。在main()中用Exception的句柄可能丢失
目标。
public class ThrowOut {
public static void main(String[] args) throws Throwable {
try {
throw new Throwable();
} catch(Exception e) {
System.out.println("Caught in main()");
}
}
}
程序中的违例不会被捕获。
7.标准java违例
Error(从Throwable继承)代表编译器和系统错误,我们一般不用捕获它们;
Exception(从Throwable继承)可以从任何标准java库的类方法中掷出的基本类型。
RuntimeException它在默认的情况下会自动得到处理。假如一个RuntimeException
获得到达main()的所有途径,同时不被捕获,那么程序退出时,会为那个违例调用
printStackTrace()。
8.创建自己的违例
必须从一个现有的违例类型继承
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
super(msg)明确调用了带有String参数的基础类构建器。也可以在新的违例中做更多
的事情,假如我们需要的话。
9.违例的限制
覆盖一个方法时,只能产生已在方法的基础类版本中定义的违例。
下面的例子书上的:
package exam;
class BaseballException extends Exception{}
class Foul extends BaseballException{}
class Strike extends BaseballException{}
abstract class Inning{
Inning() throws BaseballException{}
void event() throws BaseballException{}
abstract void atBat() throws Strike,Foul;
void walk(){}
}
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 {
StormyInning() throws RainedOut,BaseballException{}
StormyInning(String s)throws Foul,BaseballException{}
public void rainHard() throws RainedOut{}
public void event(){}
void atBat() throws PopFoul{}
public static void main(String[] args) throws RainedOut, BaseballException{
try{
StormyInning si=new StormyInning();
si.atBat();
}catch(PopFoul e){
}catch(RainedOut e){
}catch(BaseballException e){}
try{
Inning i=new StormyInning();
i.atBat();
}catch(Strike e){
}catch(Foul e){
}catch(RainedOut e){
}catch(BaseballException e){}
}
}
“interface Storm”非常有趣,因为它包含了在Incoming 中定义的一个方法——event(),以及不是在其中
定义的一个方法。这两个方法都会“掷”出一个新的违例类型:RainedOut。当执行到“StormyInning
extends”和“implements Storm”的时候,可以看到Storm 中的event()方法不能改变Inning中的event()
的违例接口。同样地,这种设计是十分合理的;否则的话,当我们操作基础类时,便根本无法知道自己捕获
的是否正确的东西。当然,假如interface 中定义的一个方法不在基础类里,比如rainHard(),它产生违例
时就没什么问题。
对违例的限制并不适用于构建器。在StormyInning 中,我们可看到一个构建器能够“掷”出它希望的任何东
西,无论基础类构建器“掷”出什么。然而,由于必须坚持按某种方式调用基础类构建器(在这里,会自动
调用默认构建器),所以衍生类构建器必须在自己的违例规范中声明所有基础类构建器违例。
StormyInning.walk()不会编译的原因是它“掷”出了一个违例,而Inning.walk() 却不会“掷”出。若允许
这种情况发生,就可让自己的代码调用Inning.walk(),而且它不必控制任何违例。但在以后替换从Inning
衍生的一个类的对象时,违例就会“掷”出,造成代码执行的中断。通过强迫衍生类方法遵守基础类方法的
违例规范,对象的替换可保持连贯性。
覆盖过的event()方法向我们显示出一个方法的衍生类版本可以不产生任何违例——即便基础类版本要产生
违例。同样地,这样做是必要的,因为它不会中断那些已假定基础类版本会产生违例的代码。差不多的道理
亦适用于atBat(),它会“掷”出PopFoul——从Foul 衍生出来的一个违例,而Foul 违例是由atBat()的基
础类版本产生的。这样一来,假如有人在自己的代码里操作Inning,同时调用了atBat(),就必须捕获Foul
违例。由于PopFoul 是从Foul 衍生的,所以违例控制器(模块)也会捕获PopFoul。
最后一个有趣的地方在main()内部。在这个地方,假如我们明确操作一个StormyInning 对象,编译器就会
强迫我们只捕获特定于那个类的违例。但假如我们上溯造型到基础类型,编译器就会强迫我们捕获针对基础
类的违例。通过所有这些限制,违例控制代码的“健壮”程度获得了大幅度改善(注释③)。
注意:尽管违例规范是由编译器在继承期间强行遵守的,但违例规范并不属于方法类型的一部分,后者仅包
括了方法名以及自变量类型。因此不可以再违例规范的基础上覆盖方法。
10.用finally清除
无论是否掷出一个违例,finally从句都会执行。finally会在违例控制机制转到更高级别的搜索一个控制器之前
得到执行。
11.丢失的违例
package exam;
class VeryImportanException extends Exception{
public String toString(){
return "A very importance exception!";
}
}
class HoHumException extends Exception{
public String toString(){
return "A trivial exception";
}
}
public class LostMessage {
void f() throws VeryImportanException{
throw new VeryImportanException();
}
void dispose() throws HoHumException{
throw new HoHumException();
}
public static void main(String[] args)throws Exception{
LostMessage lm=new LostMessage();
try{
lm.f();
}finally{
lm.dispose();
}
}
}
输出的结果:
A trivial exception
at LostMessage.dispose(LostMessage.java:21)
at LostMessage.main(LostMessage.java:29)
这里的VeryImportantException丢失掉了,被HoHumException代替了。
也就是说产生的第二个违例会把第一个违例给代替。
12.违例匹配
掷出一个违例后,违例控制系统会按当初编写的顺序搜索“最接近”的控制器,一个衍生类
对象可与基础类的一个控制器匹配。
package exam;
class Annoyance extends Exception{}
class Sneeze extends Annoyance{}
public class Human {
public static void main(String[] args){
try{
throw new Sneeze();
}catch(Annoyance a){
System.out.println("Caught Annoyance");
}
}
}
也就是说catch能捕获一个Annoyance以及从它衍生出来的任何类,当我们决定为一个方法添加更多
的违例的时候,而且他们都是从相同的基础类继承,那么客户程序员的代码就不需要修改。
13.违例准则
书上原文:
用违例做下面这些事情:
(1) 解决问题并再次调用造成违例的方法。
(2) 平息事态的发展,并在不重新尝试方法的前提下继续。
(3) 计算另一些结果,而不是希望方法产生的结果。
(4) 在当前环境中尽可能解决问题,以及将相同的违例重新“掷”出一个更高级的环境。
(5) 在当前环境中尽可能解决问题,以及将不同的违例重新“掷”出一个更高级的环境。
(6) 中止程序执行。
(7) 简化编码。若违例方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。
(8) 使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改
善应用程序的健壮性)
总结
违例差错控制,可以增强代码的健壮程度。对于我们捕获的违例,能够经过我们的处理后程序继续执行,
而不是直接崩溃,也可以防止一些意外的错误。这样的话我们能创建大型、可靠的应用程序。