对Java来说,异常就是一个类,产生了一个异常,就是产生了一个Exception类的对象,这个类代表不正常的现象。

1. 异常的继承体系

在Java中使用Exception类来描述异常。它的父类是ThrowableThrowable是Java 语言中所有错误或异常的super类。
我们再来观察Throwable类,能够发现与Exception类平级的有一个Error类。Error也是Throwable的子类,它用来表示java程序中可能会产生的严重错误。解决办法只有一个,修改代码避免Error错误的产生。

  1. Throwable(生病)
  2. Error (不可治愈的疾病) :宕机,断点,海啸
  3. Exception (能治愈的疾病) 异常,程序员能处理
  4. RuntimeExcetion 非运行时期异常,编译器异常,要求我们在编译器就必须进行处理
  5. RuntimeException(运行时期异常

2. 异常产生的后果

  • 如果对发生的异常不处理,异常会被抛出给调用函数。如果对发生的异常一直不处理,则代码停止执行,并且将异常直接抛给JVM。
  • 如果对发生的异常进行try-catch,try下的代码不会执行,此异常会被catch捕获,同时程序继续执行

    3. 异常的处理

    A. throw and throws

    在java中,提供了一个throw关键字,它用来抛出一个指定的异常对象:

    throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
    

    如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws在定义方法时声明所有可能被抛出的异常,让调用者去处理。

    public static int getElement(int[] arr,int index) throws NullPointerException, ArrayIndexOutOfBoundsException {
      if(arr==null){
          throw new NullPointerException("arr指向的数组不存在");
      }
      if(index<0 || index>=arr.length){
          throw new ArrayIndexOutOfBoundsException("错误的角标,"+index+"索引在数组中不存在");
      }
      int element = arr[index];
      return element;
    }
    

    B. try-catch-finally

    try{
      // 可能发生异常的代码
    }catch(异常类型 e1| 异常类型 e2){
      //如果产生异常,进行的处理
      e.printStackTrace() 打印 针对于程序员的
    }catch(异常类型 e3) {
      throw e3;
    }
    finally {
      //无论程序是否有异常出现, finally中的代码必须执行。一般用于释放资源
    }
    

    注意:

  • e1 和 e2 必须是同级异常

  • e3 必须是 e1 和 e2的同级或者父类如RuntimeExceptionException
  • finally返回路径:如果return发生在try块或者catch块中,每次碰到return就会在返回路径中临时存储这个被返回的值。无论finally方法内有任何的改变,返回路径中的这个值一致不变。

    C. 异常的分类

    异常分为编译异常和运行异常:

  • 编译异常(Exception 及其直接子类): 在Java中, Exception类中除了 RuntimeException类及其子类都是编译时异常。编译时异常的特点是Java编译器会对其进行检查,如果出现异常就必须对异常进行处理,否则程序无法通过编译。

处理编译时期的异常有两种方式,具体如下:
(1)使用try…catch语句对异常进行捕获。
(2)使用throws关键字声明抛出异常,调用者对其处理。

  • 运行异常(RuntimeException): RuntimeException类及其子类都是运行时异常。运行时异常的特点是Java编译器不会对其进行检查,也就是说,当程序中出现这类异常时,即使没有使用try…catch语句捕获或使用throws关键字声明抛出,程序也能编译通过。运行时异常一般是由程序中的逻辑错误引起的,在程序运行时无法恢复。比如通过数组下标访问越界,除以0.

    4. 方法重写时,throws规则

    父类的方法如果有异常抛出,子类重写后可以不抛出异常。
    父类的方法如果有异常抛出,子类重写后有异常抛出,抛出的异常不能大于(继承关系)父类异常。
    父类的方法如果没有异常抛出,子类重写后不能抛出异常。只能通过try-catch块对异常进行处理。

    5. 获取异常详情:

    Throwable类中的三个方法都和异常的信息有关系:

  • String getMessage() 获取message属性的值 异常了!

  • String toString() 对异常信息的描述 java.lang.Exception: 异常了!
  • void printStackTrace() 将异常信息追踪到标准的错误流.异常信息最全,JVM默认调用方法也是这个方法.

    public class ExceptionDemo1 {
      public static void main(String[] args) {
          try{     
              function();
          }catch(Exception ex){
              //System.out.println(ex.toString());
              ex.printStackTrace();
          }
      }
    
      public static void function() throws Exception{
          throw new Exception("异常了!");
      }
    }
    

    6. 自定义异常

    在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。
    一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。
    BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:

    public class BaseException extends RuntimeException {
    }
    

    其他业务类型的异常就可以从BaseException派生:

    public class UserNotFoundException extends BaseException {
    }
    public class LoginFailedException extends BaseException {
    }
    

    自定义的BaseException应该提供多个构造方法:

    public class BaseException extends RuntimeException {
      public BaseException() {
          super();
      }
      public BaseException(String message, Throwable cause) {
          super(message, cause);
      }
      public BaseException(String message) {
          super(message);
      }
      public BaseException(Throwable cause) {
          super(cause);
      }
    }
    

    述构造方法实际上都是原样照抄RuntimeException。这样,抛出异常的时候,就可以选择合适的构造方法。通过IDE可以根据父类快速生成子类的构造方法。