在理想状态下,程序会按照我们预想的步骤一步一步的执行,但是即使你是大牛,你也不可避免出错,所以java为我们提供了异常机制。本文将会从以下几个方面介绍java中的异常机制:

  • 异常机制的层次结构

  • 异常的处理过程

  • 抛出异常

  • 捕获异常

  • 异常机制的实现细节

一、异常机制的层次结构
      在java程序设计语言中,所有的异常对象都是派生于Throwable类,一般情况下,如果java中内置的异常类不能满足需求,可以自定义异常类只需要继承与Throwable类即可,下面是java 中的异常层次结构:

      error类表示java内部错误,例如jvm出错或者内存不足等,这层一般不用我们关心(其实我们也无可奈何,如果出现此种问题),一旦出现此种错误程序会自动结束。
      我们主要还是需要研究Exception类及其子类,Exception主要有两个子类,IOException和RuntimeException我们一个个看。IOException是IO错误类问题导致的异常,是可预知的,例如由于文件不存在而打开文件失败引起的异常等。RuntimeException类异常表示运行时异常,例如数组下标越界,访问空指针等,是不可预知的,但是这类异常于编写程序时应当予以避免,例如你可以使用if(a==null).....,加以检查避免。
      以上从一种角度对这些主要的异常类进行了分类,其实我们还有一种分类方式,按照是否是检查类异常(checked)进行分类,什么是检查类异常?检查类异常(checked)就是指编译器会检查当前的代码块中,判断是否有这么一条语句在程序执行时可能产生异常,如果有就会建议程序员处理。(实际上你必须处理,不然编译不会通过),在以上的分层中,error类和RuntimeException类属于非检查异常类(unchecked),而IOException属于检查类异常。


二、异常的处理过程

public class Test2 {
    public static void main(String[] args){
        doMaths();
    }
    public static void doMaths(){
        int a = 10/0;//此处将会出现异常
        System.out.println(a);
    }
}
输出结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at test.Test2.doMaths(Test2.java:8)
    at test.Test2.main(Test2.java:5


      相信大家在看完上文之后,可以判断出此处的异常应该属于RuntimeException,属于unchecked异常,由输出的结果可以看出:main方法中调用方法doMaths();,于是进入该方法内部,执行int a = 10/0;产生异常,在本方法中未找到处理,于是返回调用处(即main函数中),发现main函数中依然没有处理方法,于是执行默认操作(打印异常信息)。
      我们的程序难免会出现问题,但是绝对不能让用户看出来你的程序出现问题,所以这些异常信息是绝对不能让用户看见的,且不说他是否能看懂,一旦遇到“行家”,你的程序就会存在安全隐患,由此可见,对异常的处理是多么重要。下面我们将讨论两种处理异常的机制。


三、抛出异常
      以下所讨论的处理异常的方法,主要还是针对检查类异常(checked),因为对于error类的错误我们没法处理,对于RuntimeException类异常我们应当尽量在程序中加以避免,所以我们主要还是对检查类异常进行处理。
      我们使用关键字throws将本程序中可能遇到的异常向上抛出(向调用出抛出,让调用者处理),而本身不做处理。

public void test() throws AppException{
    ................
    ...............
}


      throws 紧跟方法的括号后面,这个叫异常的声明表示本方法不处理这个异常,谁调用我这个方法谁来处理(后面将讨论如何处理异常,因为总要有人来处理,否则就默认打印异常信息),可以声明多个异常,异常之间使用逗号相隔。
      那么throw关键字和throws关键字究竟有什么联系呢?throw可以理解为是在throws关键字的主动行为。先看代码:

public class Test2 {
    public static void main(String[] args){
        show();
    }
    public static void show(){
        String s = null;
        s.charAt(0);
    }
}
/*以上代码抛出NullPointerException,空指针异常*/
public static void show() throws NullPointerException{
        String s = null;
        s.charAt(0);
    }
/*你可以选择抛给调用者*/
public static void show(){
        String s = null;
        if(s==null){
            throw new NullPointerException();
        }
        s.charAt(0);
    }
/但是如果你可以判断s处可能出现异常,就可以主动抛出异常*/


      小结一下,throws关键字表示:本函数中存在某个异常但是我不知道,如果出现此异常就抛给调用者。throw一般和判断语句if配合的多表示:如果满足条件就结束此函数并向调用者抛出异常,否则就继续执行接下来的代码。


四、捕获异常
      以上说了那么多,我们知道如何将遇到的异常抛出,让别人处理,但是总是有人需要处理这个异常的,总不能让系统默认的将异常信息打印出来吧!再者说,一个strong的程序必须要有对错误的处理,让程序控制在手中而不是莫名卡死或者退出。接下来,我们一起探讨如何捕获异常对他进行处理。
      捕获异常,我们使用try{}catch{}关键字,try中代码表示可能出现异常的代码块,catch中的代码块表示捕捉到该异常之后需要进行的后续操作。

/*使用上述代码,添加try catch捕获异常*/
public class Test2 {
    public static void main(String[] args){
        try{
            show();//可能出现异常的代码块
        }catch(NullPointerException z){
            System.out.println("throw Exception");
            //捕捉到异常后的操作
        }
    }
    public static void show()throws NullPointerException {
        //将异常抛给调用者
        String s = null;
        s.charAt(0);
    }
}
输出结果:
throw Exception


      对比于之前,打印自己控制的内容是不是比默认打印异常信息更加的友好?我们常常会遇到这么一个问题:打开一个文件,准备对文件进行操作,但是在对文件的操作过程中出现异常,退出程序了,但是文件并没有被显式的关闭,造成资源浪费。
      我们常常使用关键字finally来完成类似这样的操作。finally关键字紧跟catch后面,表示无论是否发生异常都会执行的代码块。finally不是必须的可以没有。

try{
  //可能出现异常的代码块
}catch{
  //捕获异常之后的代码块
}finally{
  //肯定要执行的代码块
}


五、异常机制的实现细节
      第一个我想说的是try{}catch{}这种结构并不是唯一的,可以由多个catch语句的,对多个不同的异常进行捕获。
      第二个很重要的finally这个关键字中如果出现return等关键字程序的接下来的执行顺序问题。例如:

try{
  int a=0;
  //出现异常
  return a;
}catch(..){
 a=1;
 return a;
}finally{
 a=2;
 return a; 
}
程序返回:2;


      能想明白么?其实不难,只要知道finally语句块中是最后执行的就可以理解了,当try中出现异常,被catch接住,将a赋值为1,在返回之前跳转到finally语句块中,最后在finally语句块中返回2,这种在finally语句块中有return语句的会阻止catch中的return执行。
      所以,我们建议finally语句块中不要使用return语句。

      以上便是java异常机制的基本内容,如果错误,望大家指出。tk