代码改变世界

C\C++ 程序员从零开始学习Android - 个人学习笔记(九) - java基础 - 异常

2012-02-12 17:51  CreateLight  阅读(349)  评论(0编辑  收藏  举报

1,概述

  异常是错误处理的一种手段,相比传统的返回错误值的做法,java更推荐异常处理,因为这可以分离正常代码和错误处理代码,让代码看起来更清晰。错误一般可分为两类:

  a, 逻辑错误,这种错误都是由于程序员错误的思考造成的。

  b, 外界错误,比如用户进行错误的输入、内存用尽、硬盘空间不足、网络无链接、服务器无法访问等。

  当遇到一个错误并决定抛出异常时,首先会构建一个异常对象(在堆上,如同构建其他任何对象),然后停止当前方法的执行路径,立刻退出当前方法,并且不会返回任何值,接着异常处理机制会开始搜索这个异常的处理器,继续执行程序。

2,分类:

  2.1,所有异常类都继承于Throwable类或其子孙类,Throwable类是所有异常类的共同祖先。

  2.2,Throwable 有两个子类:

    2.2.1 Error

    Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误,应用程序不应该抛出这种类型的对象。当发生了这种异常时,除了告之用户,尽可能干净地退出外,没有太好的处理办法。这种异常很少见。

   2.2.2 Exception

    a  RuntimeException

    由于程序逻辑错误导致的异常,如果出现了RuntimeException,就一定是程序员的问题。RuntimeError的例子比如:访问空引用、数组越界访问等。程序员能够小心地编写代码,避免所有的RuntimerError。

   b  非RuntimException

   像IO错误这种依赖于环境的不可预测的错误,程序员无法通过编写代码避免所有的异常。比如URL格式错误,由于不同的浏览器可能支持不同的(特定)URL类型,这个异常的产生取决于环境。

  2.3,unchecked异常和checked异常

    所有的Error和RuntimeException称之为unchecked异常,即"未检查异常"或者更确切的“不需检查异常”,这类异常不需要也不应该有异常处理器,当出现这种异常时,应该修改代码以排除异常,而不是在异常处理器中进行处理。

    其他的异常称之为checked异常,即“已检查异常”或更确切的“必须检查异常”,这类异常必须提供对应的异常处理器,编译器将对此做强制保证。

3,使用

  异常应该用来处理非逻辑错误,是必须保留在release版本中一直起作用的代码。

  逻辑错误应该使用断言(实现契约式编程)进行诊断,通过修改代码来修正,理想状况下release版本中不需要有断言。

  自定义异常:应该总是派生自checked异常。当一个方法发现了一个错误时,它可以选择自己处理它,这是最佳选择,一个异常应该尽可能的在最近的局部进行处理;如果它不知道应该如何处理,可以向上一层抛出一个异常,由上层进行处理。这里其实存在争论,Bob大叔(R.Matin)的建议是不要使用checked异常,因为这会导致从产生异常的地方一直到最后处理异常的方法,这一路上的方法调用链都必须声明异常,这某种意义上暴露了实现细节、扩散了依赖,是一种“丑陋”的形式,他建议总是使用unchecked异常。

  unchecked异常:总是会被抛出,由java编译器和虚拟机保证。

  3.1 自己处理异常

 使用try-catch-finally语句进行异常的抛出、捕获。

  public void do()

  {

    try{

      // code - 1

      // may throw new MyException();

      // code - 2

    }catch(MyException e){

      // code - 3 

      // may throw new MyException()

               //  code - 4

    }finally{

      // code - 5

    }

      // code - 6

  }

各种情况下的执行顺序:

1, 不抛出任何异常:

  code -1 、code - 2、code - 5、 code - 6、返回调用者。

  将会执行try块中所有语句,然后跳过catch块,执行finally语句,然后顺序执行方法体直至返回。

2,try块中抛出异常,被catch块捕获:

  code - 1、code - 3、code - 4、code - 5、code - 6、返回调用者

  将会执行tyr块中的语句,直至遇到一个异常被抛出;然后跳过try块中的剩余语句,执行catch块中的语句;然后执行finally语句,然后退出方法返回至调用者。

3,try块中抛出异常,被catch块捕获,catch块中也抛出异常:

  code - 1、code - 3、code - 5、返回调用者

  将会执行tyr块中的语句,直至遇到一个异常被抛出;然后跳过try块中的剩余语句,执行catch块中的语句,直到遇到异常被抛出,然后跳过剩下的catch块中的语句,执行finally语句;然后退出方法返回至调用者。

4,try块中抛出异常,不被catch块捕获

  code - 1、code - 5、返回调用者

  将会执行tyr块中的语句,直至遇到一个异常被抛出;然后执行finally语句;然后退出方法返回调用者。

4,没有catch语句块,无论try块抛不抛出异常,都会执行finally语句块

5,try块之前的代码抛出异常

  从异常处退出方法,不执行接下来的任何语句。

6, 含有return语句:

try

{

    //code - 1;

    return 1;

}

finally

{

   //code - 2

   return 2;

}

将会执行code - 1、set return value = 1、code - 2、return 2

执行try块中的语句,遇到return语句时设置返回值为1,并不立刻返回,而是开始执行finally语句,finally语句中的return 2将返回值复写为2,所以最终执行的是return 2.

7, finally语句中抛出异常

try

{

   throw new MyException();

}

finally

{

  throws new MyException2();

}

原始的异常(MyException)将会丢失,转而抛出finally中的异常(MyException2),这并非异常处理机制希望看到的结果,因此在finally语句中最好不要抛出异常。


  3.2 抛出异常给上层处理

  a, 异常规范

  方法必须声明它将抛出某种类型的checked异常,以通知编译器及使用我们方法的程序员(称之为异常规范)。编译器会寻找对应的异常处理器,如果没有找到,则会终止相应(抛出异常的)线程。  和C++在运行时处理throw语句不同,java的throw语句是在编译时进行处理的,因此如果方法试图抛出未声明的checked异常,就会报编译错;而C++则会在运行时调用unexpected函数,这个函数的默认方式是中止程序的运行。即使代码体中没有某种异常的抛出语句,也可以声明此种异常,强迫掉用此方法的代码必须处理相应的异常 - 这等同于一个占位符,我们以后可能会回来处理(当然,对敏捷来说,这是一种不好的做法)。

  对于unchecked异常(Error和RuntimeException),不应该声明(尽管可以声明,但应该花时间在修正代码逻辑错误上,而不是靠异常处理器)。

  // 声明异常,告知编译器此方法可能抛出异常 MyCheckedException、MyCheckException2

  Public void do() throws MyCheckedException, MyCheckException2

  {

 

  }

   a.1 普通方法异常规范

  如果super类某个方法抛出一些异常,子类覆盖此方法的方法所抛出的异常范围应该不超过super类。比如:如果super.f()抛出1、2、3三个异常;那么sub.f()只能抛出1、2、3的子集。这是为了保证类对象的可替换性。

   a.2 构造器方法异常规范

  构造器也可以抛出异常,子类的构造器必然会调用某个父类的构造器,如果调用了父类的带有异常声明的构造器,也必须声明相应父类构造器的异常;与普通方法不同的是,子类的构造器可以声明更多的异常。

  a.3 接口方法异常规范

  接口不能给实现接口的类方法添加新异常声明

 接口中的方法 f() 声明了某个异常 E1,一个类实现了这个方法,如果该类的父类不包括方法 f() (即没有发生方法复写),则子类中的 f() 可以声明E1;

 如果该类的父类包含了f(),声明异常 E2,则子类只能在父类的异常种类范围内声明异常(E2或不声明),而不能声明接口方法中的E1.

  b, 异常包装

  当存在异常链的时候,可以使用异常包装的机制来保存原始异常的信息,以供上层处理器恢复:

  try

  {

    throw new Exception1();

  }

  catch(Exception1 e1)

  {

    Exception2 e2 = new Exception("xxx", e1);

    // or Exception2 e2 = new Exception("xxx"); e2. initCause(e1);

  throw e2;

  }

  上层异常处理器可以用 e2.getCause()来获取原始异常。

3.3 、如何使用异常

基本原则:异常是为了异常情况而使用的。

  1,异常的创建非常耗时,因此不能代替简单的判断语句,只应在很少发生的异常情况下使用。单纯的try-catch语句并不会造成多大的性能损失,只有在异常被触发时,需要创建一个异常对象,这时需要收集一个栈跟踪(stack trace),这通常是通过对运行时栈做一份快照来实现的,如果栈的层次很深,开销会非常大。

  2,不要过分细化异常,try语句块中可放置一个功能的所有语句,而不需要每一步配一个try。

  3,利用异常的层次结构,不要只是抛出RuntimeException,不要只是catch Thowable异常。在合适的时候转化异常类型,使之与当前的抽象类型一致,而不是暴露底层的实现细节。

  4,一般应用中不使用unchecked异常,除非异常是无法被合适处理的(可能唯一的方法就是关闭程序)。