异常的定义

在程序的运行中,我们会遇到形形色色的异常:文档读取时出bug;联网播放视频时不停丢包;计算器算数据的时候发现除数是0,等等等等。为了方便统一处理这些问题,大多数面向对象语言都引入了异常的概念:出现问题时抛出一个异常,丢给程序来处理(程序也可以丢给他的上一级,以此类推),这样简化了程序应对突发情况的代码编写难度,提高了代码的容错率(鲁棒性)和可维护性。

Java 的基本异常框架

模板代码及解释

  1. try {
  2. //something
  3. }
  4. catch (Exception e1) {
  5. //solve 2
  6. }
  7. catch (Exception e2) {
  8. //solve 2
  9. }
  10. catch (FileNotFoundException | ParseException e) {
  11. //solve 3 or 4
  12. //紧凑写法,常常用于处理一类处理手法相似的异常
  13. //如果需要分的很清楚,还是分开写吧
  14. //非得这么写,那就用 instanceof 吧
  15. }
  16. //还可以继续接
  17. //
  18. finally {
  19. //最终执行这里的代码
  20. }

我们在 try 代码块中执行可能会抛出异常的语句,一旦检测到异常,立刻和 catch 里面进行比对。
Exception 是一个类,是所有异常的父类(所有的异常都是一个对象,对象里面包含着出错的信息之类的)。我这里图方便只写父类,实际上我们在执行代码的时候是比较清楚他大概会抛出啥异常,所以 catch 还是写仔细点比较好。程序会自动匹配到第一个匹配的 catch 块,随后执行内部语句(执行完之后不会回到 try 块继续运行,而是直接进入 finally)。
不管是顺利 try 完还是碰上了 catch,运行结束后全部进入 finally 环节,执行内部代码。
对于一个异常框架,try 模块必须具备(不然哪来的异常),catch 和 finally 必须有一个(一般都是 catch)。

关于try,catch,finally的执行顺序

1.正常情况

前面我们讲过了大体的运行流程,即try-catch-finally。不过现在我们想要探讨一些更加棘手的问题:有return,抛出异常等扰乱正常程序流程的情况呢?

2. try/catch 语句内部含有 return

public class Main {
    public static int test() {
        try {
            System.out.println(1);
            return 1;
        }
        catch (Exception e) {
            System.out.println(1);
            return 2;
        }
        finally {
            System.out.println(3);
        }
    }
    public static void main(String []args) {
        System.out.printf("result=%d", test());
    }
}

执行结果:

1
3
result=1

没错,即使try里面已经返回了,但是 finally 里面还是会运行,执行完了 finally 代码块之后才会return。

3.try/catch 和 finally 内部都含有 return

public class Main {
    public static int test() {
        try {
            System.out.println(1);
            return 1;
        }
        catch (Exception e) {
            System.out.println(2);
            return 2;
        }
        finally {
            System.out.println(3);
            return 3;
        }
    }
    public static void main(String []args) {
        System.out.printf("result=%d", test());
    }
}

执行结果:

1
3
result=3

与此同时,编译器会警报 finally block does not complete normally ,原因如下:

  1. 不管 try,catch 里面有没有 return,finally 照常执行
  2. finally 里面的 return 会覆盖 try 和 catch 里面的 return(如果有的话)

    4. finally 内部都含有 return,try/catch 内部会抛出异常

    不懂啥叫抛出异常的可以先看下面,待会再来看这个
    没错,和 return 类似,即使你抛出异常,但是程序依旧会进入 finally 中,并且正常执行 return,你的上一级程序并不会识别到异常,只会得到 finally 的返回值。

    5. try/catch 内部含有特殊语句/方法等(例如exit())

    finally 再怎么优先,至少是遵循 Java 语法规则的,碰上直接用 exit 这一类系统函数的还是得老老实实退场。

    finally:try/catch 这两个年轻人不讲武德,来骗,来偷袭,我这个69岁的老同志,这好吗?这不好

总结

  1. 正常情况下,遵循 try-catch-finally 顺序
  2. try/catch 中有 return 或者 throw,当执行到这步时,依旧得跳到 finally,先执行完里面的语句,随后再 return 或者 throw(如果finally里面就有 return/throw,那么方法/程序就照着finally 里面这个退回上级了)
  3. 对于exit,强行删除进程,内存修改这些“超自然”力量而言,以上法则并不适用(逃

    异常的分类

    1. 可查异常(CheckedException)

    这类异常是必须处理的异常,要么 catch 住,要么往外抛,不能不处理。
    这种不处理是指代码编写层面的,如果不处理,根本没法编译成功。打个比方,如果你调用一个文件处理的方法,恰好这个文件处理的方法可能会抛出一个可查异常(例如 FileNotFoundException),那么你的代码必须得在运行他的时候准备好处理措施,否则会被拒绝编译。 ```java import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException;

public class TestException {

public static void main(String[] args) {

    File f= new File("d:/LOL.exe");
    /*
    new FileInputStream(f);
    没有准备好异常处理就来调用?想得美,直接给你报错怎么说
    */
    try{
        System.out.println("试图打开 d:/LOL.exe");
        new FileInputStream(f); //必须准备好try-catch框架才能执行
        System.out.println("成功打开");
    }
    catch(FileNotFoundException e){
        System.out.println("d:/LOL.exe不存在");
        e.printStackTrace();
    }

}

}

<a name="kbnoF"></a>
## 2. 运行时异常(RuntimeException)
这类异常在程序运行时产生,无需在编码层面进行处理,常见的有除零异常,下标越界异常,空指针异常等等。<br />之所以无需处理,是因为这类异常太常见了,几乎每行代码都有可能出现,如果都要一一处理,那能给程序员写麻了(当然,如果你预见到可能会有异常,还是先准备好处理方法吧)。
```java
public class TestException {
    public static void main(String[] args) {

        //任何除数不能为0:ArithmeticException
        int k = 5/0;

        //下标越界异常:ArrayIndexOutOfBoundsException
        int j[] = new int[5];
        j[10] = 10;

        //空指针异常:NullPointerException
        String str = null;
        str.length();
   }
}

3. 错误(Error)

这类异常大多数都是系统级别的,例如内存用光了啥的乱七八糟的。和第二类异常一样,这个也不需要强制处理
附:Error 和 Expection 是同一级的(所以严格讲,错误并不是异常的一种),他们有一个共同的父类 Throwable。

抛出异常

有时候我们并不想自己在本方法内部处理异常,而是将其抛给上一级,那么我们可以使用 throws 来解决。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class TestException {

    public static void main(String[] args) {
        method1();
    }

    private static void method1() {
        try {
            method2();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
    private static void method2() throws FileNotFoundException {

        File f = new File("d:/LOL.exe");

        System.out.println("试图打开 d:/LOL.exe");
        new FileInputStream(f);
        System.out.println("成功打开");

    }
}

在函数后面加上 throws,表明这个函数可能会抛出一个异常。
不难注意到,method2 方法有可能产生一个异常,那干脆都不用自己 throw 了,检测到之后发现没准备 try-catch框架,直接自动给扔出去了(逃
throw 具体咋用,等到自定义异常那边再细讲吧
注:如果框架只有 try 和 finally,那你必须得在函数上加上 throws。

自定义异常

很多时候,系统自带的异常并不能够满足我们的需要,所以我们常常需要自己编写异常。

class GcdException extends Exception {
    String errInf = "gcd的两个数并非都是正整数";
    public void printErr() {
        System.out.print(errInf);
    }
}
class MyMath {
    public static int gcd(int a, int b) throws GcdException{
        if (a < 0 || b < 0)
            throw new GcdException();
        if (a == 0) return b;
        if (b == 0) return a;
        return gcd(b, a % b);
    }
}
public class Main {
    public static void main(String []args) {
        int a = 36, b = -24;
        try {
            System.out.printf("gcd is %d", MyMath.gcd(a, b));
        }
        catch (GcdException e) {
            e.printErr();
        }
    }
}

所有的自定义异常都是可查异常,所以在使用的时候必须搭好 try-catch 框架。