---
title: Java异常处理
date: 2020-10-11 14:24:00
tags: 
- Java
- 面向对象
categories:
- Java基础
---

## 什么是异常

异常是在程序中导致程序中断运行的一种指令流。

先来看如下代码

```java
public class ExceptionDemo{
	public static void main(String[] args){
        int i = 10 ;
        int j = 0 ;
        System.out.println("============= 计算开始 =============") ;
        System.out.println(i + "/" + j + "=" + i / j);
        System.out.println("============= 计算结束 =============") ;
	}
};
```

<!--more-->

运行结果：

```java
============= 计算开始 =============
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ExceptionDemo01.main(ExceptionDemo01.java:6)
```

以上的代码在`System.out.println(i + "/" + j + "=" + i / j);`位置处产生了异常`ArithmeticException(算术异常)`，一旦产生异常之后，异常之后的语句将不再执行了，所以现在的程序并没有正确的执行完毕之后就退出了。

那么，为了保证程序出现异常之后仍然可以正确的执行完毕，所以要采用异常的处理机制。

## 异常处理

如果要想对异常进行处理，则必须采用标准的处理格式，处理格式语法如下：

```java
try{
	// 有可能发生异常的代码段
}catch(异常类型1 对象名1){
	// 异常的处理操作
}catch(异常类型2 对象名2){
    // 异常的处理操作
} ...
finally{
	// 异常的统一出口
}
```

将开头的代码用try-catch处理：

```java
public class ExceptionDemo{
	public static void main(String[] args){
        int i = 10 ;
        int j = 0 ;
        System.out.println("============= 计算开始 =============") ;
        try{
            System.out.println(i+ "/" + j + "=" + i /j);
        }catch(ArithmeticException e){
            System.out.println("除数不能为0");
        }
        System.out.println("============= 计算结束 =============") ;
    }
};
```

运行结果

```bash
============= 计算开始 =============
除数不能为0
============= 计算结束 =============
```

###  try+catch的处理流程

1、 一旦产生异常，则系统会自动产生一个异常类的实例化对象。

2、 那么，此时如果异常发生在try语句，则会自动找到匹配的catch语句执行，如果没有在try语句中，则会将异常抛出，抛给调用方法者

3、 所有的catch根据方法的参数匹配异常类的实例化对象，如果匹配成功，则表示由此catch进行处理。

![](https://i.loli.net/2020/10/10/bVzw1mud4eif7ot.png)

### finally

在进行异常的处理之后，在异常的处理格式中还有一个finally语句，那么此语句将作为异常的统一出口，<span style="color:red">不管是否产生了异常，最终**必然都要**执行此段代码</span>。

>   finally例子1：
>
>   ```java
>   public class ExceptionDemo {
>      public static void main(String[] args){ 
>          try {
>              System.out.println(1);
>              System.out.println(2);
>              return;
>          }catch (Exception e) {
>              return;
>          }finally {
>              System.out.println("finally代码块中的内容");
>          }
>      }
>   };
>   ```
>
>   运行测试结果：说明即使在try和catch中return，在准备返回值与跳出函数之间，仍然会执行finally中的语句
>
>   ![image-20201010164546702](https://i.loli.net/2020/10/10/x5eOw9uGgMRCEdZ.png)
>
>   finally例子2：
>
>   ```java
>   public class ExceptionDemo {
>      public static void main(String[] args){
>          Person p = testFinally();
>          System.out.println(p.age);		//28
>      }
>      public static Person testFinally(){
>          Person p = new Person();
>          try {
>              p.age = 18;
>              return p;
>          }catch (Exception e) {
>              return null;
>          }finally {
>              p.age = 28;
>          }
>      }
>      static class Person{
>          int age;
>      }
>   };
>   ```
>
>   finally例子3：
>
>   ```java
>   public class ExceptionDemo {
>      public static void main(String[] args){
>          int a = testFinally();
>          System.out.println(a);		//10
>   
>      }
>      public static int testFinally(){
>          int a = 10;
>          try {
>              return a;
>          }catch (Exception e) {
>              return 0;
>          }finally {
>              a = 20;
>          }
>      }
>   };
>   ```
>
>   注意例子2和例子3，我们 testFinally() 方法一个返回的是引用数据类型，一个返回的是基本数据类型。例子3中返回非引用数据类型时return 备份的就是数据10，所以运行结果是10，不过此时栈中a的数据还是被改成20了。而在例子2中，我们return备份的是引用类型对象 p 在堆中的地址，存放在堆中那个地址的age被改为了28，当我们return 通过地址去找age时，就是会返回28。

### 异常处理真实场景举例

```java
import java.util.InputMismatchException;
import java.util.Scanner;

public class ExceptionDemo2 {
    public static void main(String[] args) {
        int num = menu();
        System.out.println("您选择的序号是" + num);

    }
    public static int menu(){
        System.out.println("请根据提示，选择功能序号：");
        System.out.println("0.退出\n1.增\n2.删\n3.改");
        Scanner input = new Scanner(System.in);
        int num = -1;
        try {
            num = input.nextInt();
            if (num<0 || num>3){
                System.out.println("输入的序号必须是：0/1/2/3");
                return menu();//如果数字范围超过预期，递归重新调用此函数
            }
            return num;
        }catch (InputMismatchException e){
            System.out.println("输入必须是数字");
            return menu();  //如果输入非数字，递归重新调用此函数
        }
    }
}
```

运行效果测试：

![image-20201010161246734](https://i.loli.net/2020/10/10/lKxIRXjcQ4SzuGh.png)

## 异常体系结构

异常指的是Exception ， Exception类， 在Java中存在一个父类Throwable（可能的抛出）

Throwable存在两个子类：

1.  Error：表示的是错误，是JVM发出的错误操作,只能尽量避免，无法用代码处理。
2.  Exception：一般表示所有程序中的错误，所以一般在程序中将进行try…catch的处理。

![](https://i.loli.net/2020/10/10/5WiUkG6aMdAOwYo.png)

>   多异常捕获的注意点：
>
>   1.  捕获更粗的异常不能放在捕获更细的异常之前。
>   2.  如果为了方便，则可以将所有的异常都使用Exception进行捕获。
>
>   特殊的多异常捕获写法：
>
>   ```java
>   catch(异常类型1 |异常类型2 对象名){
>   	//表示此块用于处理异常类型1 和 异常类型2 的异常信息
>   }
>   ```
>
>   ```java
>   import java.util.Scanner;
>   
>   public class ExceptionDemo {
>       public static void main(String[] args){
>           try {
>               Scanner input = new Scanner(System.in);
>               System.out.println("请输入一个数字：");
>               int x = input.nextInt();
>               System.out.println("请再输入一个数字：");
>               int y = input.nextInt();
>               System.out.println(x/y);
>           }catch (InputMismatchException | ArithmeticException e) {		//格式一
>               System.out.println("输入错误");
>           }
>           /*
>            catch(RuntimeException e（或Exception e）){	//扩大异常的形态范围来捕获 ，格式二
>            	System.out.println("输入错误");
>            } 	
>            */
>           System.out.println("程序执行完毕，正常结束");
>       }
>   };
>   ```

## throws和throw关键字

###throws

在程序中异常的基本处理已经掌握了，但是随异常一起的还有一个称为throws关键字，此关键字主要在方法的声明上使用，表示方法中不处理异常，而交给调用处处理。格式如下：

```java
返回值 方法名称()throws Exception{
    
}
```

如果是因为传入的参数导致异常的发生，则可以通过throws抛出异常。通常是谁调用谁处理；

如下代码，只有传入参数出错，程序才会出错。

![image-20201010184935241](https://i.loli.net/2020/10/10/XwxopzQTY85asmi.png)

我们可以用谁调用谁处理的策略，使用throws关键字来处理异常

![image-20201010191247867](https://i.loli.net/2020/10/10/t2qHGzpw1Lgfr7s.png)

###throw

throw关键字表示在程序中人为的抛出一个异常，因为从异常处理机制来看，所有的异常一旦产生之后，实际上抛出的就是一个异常类的实例化对象，那么此对象也可以由throw直接抛出。（真正应用时，自己造异常还是比较麻烦的，之前加判断也可以出现相同的效果，所以用的较少）

代码： `throw new Exception("抛着玩的。");`

看下面的代码

```java
public class ExceptionDemo8 {
    public static void main(String[] args){
        Person p = new Person();
        p.setAge(-1);//传入非法参数
    }
};
class Person {
    int age;
    public void setAge(int age) {	//处理异常时机：谁调用谁处理
        //之前在设计此函数时，当用户输入了不合理时，自动设为1，
        //但用户在没有任何提示的情况下，输入和实际展示不一致，者本身就是异常
        if (age < 0 || age > 150){
            //this.age = 1;
            //所以在发生异常时，需要告诉调用函数，发生了什么问题，而不是自己默默处理
            throw new RuntimeException("年龄不合理");
        }else{
            this.age = age;
        }     
    }
}
```

运行结果：

![image-20201010173420321](https://i.loli.net/2020/10/10/7ciUMJSWOrK5aE9.png)

## RuntimeExcepion与Exception的区别

注意观察如下方法的源码：

Integer类：` public static int parseInt(String text)throws NumberFormatException`

此方法抛出了异常， 但是使用时却不需要进行try…catch捕获处理，原因：

因为`NumberFormatException`并不是`Exception`的直接子类，而是`RuntimeException`的子类，<span style="color:red">只要是`RuntimeException`的子类，则表示程序在操作的时候可以不必使用try…catch进行处理，如果有异常发生，则由JVM进行处理。</span>当然，也可以通过try…catch处理。

## 自定义异常类

>   编写一个类， 继承Exception，并重写一参构造方法 即可完成自定义**受检异常**类型。受检异常必须明确的处理或者抛出，否则编译不通过
>
>   编写一个类， 继承RuntimeExcepion，并重写一参构造方法 即可完成自定义**运行时异常（非受检异常）**类型。
>
>   例如：
>
>   ```java
>   class MyException extends Exception{ // 继承Exception，表示一个自定义异常类
>   	public MyException(String msg){
>   		super(msg) ; // 调用Exception中有一个参数的构造
>   	}
>   };
>   ```
>
>   自定义异常可以做很多事情， 例如：
>
>   ```java
>   class MyException extends Exception{
>   	public MyException(String msg){
>   		super(msg) ;
>   		//在这里给维护人员发短信或邮件， 告知程序出现了BUG。
>   	}
>   };
>   ```

## 异常处理常见题目

1.  **try-catch-finally 中哪个部分可以省略？**

    答： catch和finally可以省略其中一个 ， catch和finally不能同时省略
    注意:格式上允许省略catch块, 但是发生异常时就不会捕获异常了,我们在开发中也不会这样去写代码.

2.  **try-catch-finally 中，如果 catch 中 return 了，finally 还会执行吗？**

    答：finally中的代码会执行

    >   详解：
    >
    >   ​		执行流程：
    >
    >   1.  先计算返回值， 并将返回值存储起来， 等待返回
    >   2.  执行finally代码块
    >   3.  将之前存储的返回值， 返回出去；
    >
    >   ​       注意：
    >
    >   1.  返回值是在finally运算之前就确定了，并且缓存了，不管finally对该值做任何的改变，返回的值都不会改变
    >   2.  finally代码中不建议包含return，因为程序会在上述的流程中提前退出，也就是说返回的值不是try或catch中的值
    >   3.  如果在try或catch中停止了JVM，则finally不会执行。例如停电，或通过如下代码退出
    >       JVM：`System.exit(0);` 

