## try-catch-finally

“二哥，[上一节](https://mp.weixin.qq.com/s/fXRJ1xdz_jNSSVTv7ZrYGQ)你讲了异常处理机制，这一节讲什么呢？”三妹问。

“该讲 try-catch-finally 了。”我说，“try 关键字后面会跟一个大括号 `{}`，我们把一些可能发生异常的代码放到大括号里；`try` 块后面一般会跟 `catch` 块，用来处理发生异常的情况；当然了，异常不一定会发生，为了保证发不发生异常都能执行一些代码，就会跟一个 `finally` 块。”

“具体该怎么用呀，二哥？”三妹问。

“别担心，三妹，我一一来说明下。”我说。

`try` 块的语法很简单：

```java
try{
// 可能发生异常的代码
}
```

“注意啊，三妹，如果一些代码确定不会抛出异常，就尽量不要把它包裹在 `try` 块里，因为加了异常处理的代码执行起来要比没有加的花费更多的时间。”

`catch` 块的语法也很简单：

```java
try{
// 可能发生异常的代码
}catch (exception(type) e(object)){
// 异常处理代码
}
```

一个 `try` 块后面可以跟多个 `catch` 块，用来捕获不同类型的异常并做相应的处理，当 try 块中的某一行代码发生异常时，之后的代码就不再执行，而是会跳转到异常对应的 catch 块中执行。

如果一个 try 块后面跟了多个与之关联的 catch 块，那么应该把特定的异常放在前面，通用型的异常放在后面，不然编译器会提示错误。举例来说。

```java
static void test() {
    int num1, num2;
    try {
        num1 = 0;
        num2 = 62 / num1;
        System.out.println(num2);
        System.out.println("try 块的最后一句");
    } catch (ArithmeticException e) {
        // 算术运算发生时跳转到这里
        System.out.println("除数不能为零");
    } catch (Exception e) {
        // 通用型的异常意味着可以捕获所有的异常，它应该放在最后面，
        System.out.println("异常发生了");
    }
    System.out.println("try-catch 之外的代码.");
}
```

“为什么 Exception 不能放到 ArithmeticException 前面呢？”三妹问。

“因为 ArithmeticException 是 Exception 的子类，它更具体，我们看到就这个异常就知道是发生了算术错误，而 Exception 比较泛，它隐藏了具体的异常信息，我们看到后并不确定到底是发生了哪一种类型的异常，对错误的排查很不利。”我说，“再者，如果把通用型的异常放在前面，就意味着其他的 catch 块永远也不会执行，所以编译器就直接提示错误了。”

“再给你举个例子，注意看，三妹。”

```java
static void test1 () {
    try{
        int arr[]=new int[7];
        arr[4]=30/0;
        System.out.println("try 块的最后");
    } catch(ArithmeticException e){
        System.out.println("除数必须是 0");
    } catch(ArrayIndexOutOfBoundsException e){
        System.out.println("数组越界了");
    } catch(Exception e){
        System.out.println("一些其他的异常");
    }
    System.out.println("try-catch 之外");
}
```

这段代码在执行的时候，第一个 catch 块会执行，因为除数为零；我再来稍微改动下代码。

```java
static void test1 () {
    try{
        int arr[]=new int[7];
        arr[9]=30/1;
        System.out.println("try 块的最后");
    } catch(ArithmeticException e){
        System.out.println("除数必须是 0");
    } catch(ArrayIndexOutOfBoundsException e){
        System.out.println("数组越界了");
    } catch(Exception e){
        System.out.println("一些其他的异常");
    }
    System.out.println("try-catch 之外");
}
```

“我知道，二哥，第二个 catch 块会执行，因为没有发生算术异常，但数组越界了。”三妹没等我把代码运行起来就说出了答案。

“三妹，你说得很对，我再来改一下代码。”

```java
static void test1 () {
    try{
        int arr[]=new int[7];
        arr[9]=30/1;
        System.out.println("try 块的最后");
    } catch(ArithmeticException | ArrayIndexOutOfBoundsException e){
        System.out.println("除数必须是 0");
    }
    System.out.println("try-catch 之外");
}
```

“当有多个 catch 的时候，也可以放在一起，用竖划线 `|` 隔开，就像上面这样。”我说。

“这样不错呀，看起来更简洁了。”三妹说。

`finally` 块的语法也不复杂。

```java
try {
    // 可能发生异常的代码
}catch {
   // 异常处理
}finally {
   // 必须执行的代码
}
```

在没有 `try-with-resources` 之前，finally 块常用来关闭一些连接资源，比如说 socket、数据库链接、IO 输入输出流等。

```java
OutputStream osf = new FileOutputStream( "filename" );
OutputStream osb = new BufferedOutputStream(opf);
ObjectOutput op = new ObjectOutputStream(osb);
try{
    output.writeObject(writableObject);
} finally{
    op.close();
}
```

“三妹，注意，使用 finally 块的时候需要遵守这些规则。”

- finally 块前面必须有 try 块，不要把 finally 块单独拉出来使用。编译器也不允许这样做。
- finally 块不是必选项，有 try 块的时候不一定要有 finally 块。
- 如果 finally 块中的代码可能会发生异常，也应该使用 try-catch 进行包裹。
- 即便是 try 块中执行了 return、break、continue 这些跳转语句，finally 块也会被执行。

“真的吗，二哥？”三妹对最后一个规则充满了疑惑。

“来试一下就知道了。”我说。

```java
static int test2 () {
    try {
        return 112;
    }
    finally {
        System.out.println("即使 try 块有 return，finally 块也会执行");
    }
}
```

来看一下输出结果：

```
即使 try 块有 return，finally 块也会执行
```

“那，会不会有不执行 finally 的情况呀？”三妹很好奇。

“有的。”我斩钉截铁地回答。

- 遇到了死循环。
- 执行了 `System. exit()` 这行代码。

`System.exit()` 和 `return` 语句不同，前者是用来退出程序的，后者只是回到了上一级方法调用。

“三妹，来看一下源码的文档注释就全明白了！”

![](https://cdn.jsdelivr.net/gh/itwanger/jmx-java/images/exception/try-catch-finally-01.png)

至于参数 status 的值也很好理解，如果是异常退出，设置为非 0 即可，通常用 1 来表示；如果是想正常退出程序，用 0 表示即可。

“好了，三妹，关于 try-catch-finally 我们就讲到这吧！”我说。

“好的，二哥，已经很清楚了，我很期待下一节能讲 try-with-resources。”哈哈哈哈，三妹已经学会提新要求了，这令我感到非常的开心。

“没问题，下期见~”
