Java--违例控制(异常处理)

违例发生时Java处理过程:

  1. 首先,创建违例对象:在内存“堆” 里,用new来创建。
  2. 随后,停止当前执行路径(记住不可沿这条路径继续下去),然后从当前的环境中释放出违例对象的句柄。
  3. 此时,违例控制机制会接管一切,并开始查找一个恰当的地方,用于继续程序的执行。这个恰当的地方便是“违例控制器”(Java的catch块),它的职责是从问题中恢复,使程序要么尝试另一条执行路径,要么简单地继续。

违例属于对象,用new在内存堆里创建,并需调用一个构建器。在所有标准违例中,存在着两个构建器:第一个是默认构建器,第二个则需使用一个字串自变量,使我们能在违例里置入相关信息:

if(t==null)
    throw  new NullPointerException();     //第一种方法
    //throw new NullPointerException("t = null");     //第二种方法

语法:

try{
    //可能产生违例的代码;
}catch(Type1 id1){
    //处理类型1违例
}catch(Type2 id2){
    //处理类型2违例
}catch(Type3 id3){
    //处理类型3违例
}finally{
    //每次都会发生的情况
}
  • try块:try块为警戒区,可能产生违例的代码放在这里面。
  • catch块:catch块为违例控制器,针对生成的违例进行捕获。可能捕获的每种违例类型都要有一个相应的违例控制器。
  • finally块:无论违例是否在try块中发生,都要执行的代码可以放在finally块中。

违例规范:

违例规范属于方法声明的一部分,位于自变量(参数)列表的后面。违例规范采用throws关键字,后面跟随全部潜在的违例类型。

void f() throws tooBig, tooSmall, divZero { //... 

如果一个方法声明了违例,但没有throw违例,这是可以的。这样可以强迫使用这个方法的程序员当作真的产生了那个违例去对待。在实际应用中,它是一个“占位符”的作用,方便该方法以后添加违例无需修改现成的代码。

捕获所有违例:

所有违例的基础类是Exception类,所以catch(Exception e)可以捕获任何违例。也是这个原因,我们无法从Exception类获得太多信息,但Exception类继承自Throwable类,一些Throwable类的方法会有帮助:

  • String getMessage()    //获取详细的信息
  • String toString()          //返回对 Throwable 的一段简要说明,其中包括详细的消息。 
  • void printStackTrace() 和 void printStackTrace(PrintStream)        //打印出 Throwable 和 Throwable 的调用堆栈路径。调用堆栈显示出将我们带到违例发生地点的方法调用的顺序。 第一个版本会打印出标准错误,第二个则打印出我们的选择流程。

重新“throw”出违例:

catch块可以不对捕获的违例进行处理,可以简单的再次抛出。这里会出现路径问题--就是上面的printStackTrace()方法。

如果只是简单地再次抛出, 我们打印出来的、与printStackTrace()内的那个违例有关的信息会与违例的起源地对应,而不是与重新掷出它的地点对应。若想重置新的堆栈跟踪信息,可调用 fillInStackTrace(),它会返回一个特殊的违例对象。这个违例的创建过程如下:将当前堆栈的信息填充到原来的违例对象里。

public static void g() throws Throwable {
    try {
        f();    //throw e;的起点
    } catch(Exception e) {
          throw e; 
          // throw e.fillInStackTrace(); // 重新定位,throw e.fillInStackTrace();的起点
    }
}

几乎永远不必专门捕获一个“运行期违例”—— RuntimeException ——它在默认情况下会自动得到处理。

编写自己的违例:

为创建自己的违例类,必须从一个现有的违例类型继承——最好在含义上与新违例近似。其实创建一个自己的违例非常简单:

class MyException extends Exception {
    public MyException() {}
    public MyException(String msg) {
        super(msg);
    }
}

接下来就像标准违例一样使用就行。创建自己的继承类和普通类一样,继承自标准违例之后,可以增添自己的构建器、方法、字段等。

违例的限制:

覆盖一个方法时,只能产生已在方法的基础类版本中定义的违例。

违例的限制情况比较多,下面用一个例子来说明:

class Exception1 extends Exception {}
class Exception2 extends Exception1 {}
class Exception3 extends Exception1 {}
class Exception4 extends Exception {}
class Exception5 extends Exception4{}
class Exception6 extends Exception2 {}

//基础类
abstract class Inning {    
    Inning() throws Exception1 {}
    void event () throws Exception1 {
        //实际不抛出任何异常
    }
    abstract void atBat() throws Exception2, Exception3;
    void walk() {} 
}

//接口
interface Storm {    
    void event() throws Exception5;
    void rainHard() throws Exception5;
}

//继承基础类并实现接口
public class StormyInning extends Inning implements Storm { 
    //可以为构造器添加新违例,但必须处理基础构造器的违例(这里Exception1是必须要处理的)
    StormyInning() throws Exception1, Exception5 {}
    StormyInning(String s) throws Exception1, Exception2 {}
    //常规方法必须符合基类
    !void walk() throws Exception6 {} //编译错误,因为基类没有违例
    //接口不能向基类中的现有方法添加异常:
    !public void event() throws Exception5 {}//编译错误,基类中event()方法没有抛出Exception5违例(即使接口中有)
    //如果该方法基类中没有,那么可以添加新违例:
    public void rainHard() throws Exception5 {}
    //即使基类中抛出违例,现有类中也可以不抛出:
    public void event() {}
    //重写的方法会抛出继承的异常:
    void atBat() throws Exception6 {}
    public static void main(String[] args) {
        try {
            //派生版本中不会触发Exception3
            StormyInning si = new StormyInning();
            si.atBat();
        } catch(Exception6 e) {
        } catch(Exception5 e) {
        } catch(Exception1 e) {}
        try {
            //上溯造型必须捕获方法的基类版本中的异常:
            Inning i = new StormyInning();
            i.atBat();
        } catch(Exception3 e) {
        } catch(Exception2 e) {
        } catch(Exception5 e) {
        } catch(Exception1 e) {}
    }
} 

我们必须认识到这一点:尽管违例规范是由编译器在继承期间强行遵守的,但违例规范并不属于方法类型的 一部分,后者仅包括了方法名以及自变量类型。因此,我们不可在违例规范的基础上覆盖方法。除此以外, 尽管违例规范存在于一个方法的基础类版本中,但并不表示它必须在方法的衍生类版本中存在。这与方法的 “继承”颇有不同(进行继承时,基础类中的方法也必须在衍生类中存在)。换言之,用于一个特定方法的 “违例规范接口”可能在继承和覆盖时变得更“窄”,但它不会变得更“宽”——这与继承时的类接口规则是正好相反的。 

违例匹配:

“掷”出一个违例后,违例控制系统会按当初编写的顺序搜索“最接近”的控制器。一旦找到相符的控制器,就认为违例已得到控制,不再进行更多的搜索工作。在违例和它的控制器之间,并不需要非常精确的匹配。一个衍生类对象可与基础类的一个控制器相配。

因为上面的原因,越基础的违例类应该放在违例列表的下面,而越具体(特殊)的违例类应该放在违例列表的上面。

违例准则:

用违例做下面这些事情:

  1. 解决问题并再次调用造成违例的方法。
  2. 平息事态的发展,并在不重新尝试方法的前提下继续。
  3. 计算另一些结果,而不是希望方法产生的结果。
  4. 在当前环境中尽可能解决问题,以及将相同的违例重新“掷”出一个更高级的环境。
  5. 在当前环境中尽可能解决问题,以及将不同的违例重新“掷”出一个更高级的环境。
  6. 中止程序执行。
  7. 简化编码。若违例方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。
  8. 使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改 善应用程序的健壮性)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值