---
url: /notes/java-se/异常处理/index.md
---
# 异常处理

**异常** ：指的是程序在**执行过程** 中，出现的非正常情况，最终导致了JVM的非正常停止

异常**并不是指语法错误** ，如果语法错误，编译就不会通过，不会产生字节码文件

在java中异常本身就是一些类

> NullPointException

异常的**根类** ：`Throwable`类

两个分支：

`Error`严重的错误，无法通过处理的错误❌，只能事先去避免，无法通过处理

`Exception`表示异常，异常产生后，可以通过代码的方法纠正，使得程序继续运行，必须要去处理

# Throwable

## 常用方法

`public void printStackTrace()` 打印异常的信息，类型System.out.println()
`public String getMessage() `获取异常发生的原因
`public String toString()` 获取异常的类型和异常描述信息

## Exception

### 非受检异常`RuntimeException`（运行时异常）

```java
int arr[] = {1, 34, 32, 5};
System.out.println(arr[6]);
System.out.println(111);
```

![image-20211124230240662](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124230240662.png)

### 常见异常

### 受检异常`CheckedException`（编译时异常）

```java
public static void main(String[] args) throws FileNotFoundException {
       f3();
}
public static void f3() throws FileNotFoundException {
        FileInputStream fis=new FileInputStream(new File("Demo.java"));
        System.out.println(fis);
}
```

## Error

指 java 运行时**系统的内部错误** 和**资源耗尽错误** ​

```java
public static void main(String[] args){
  f2();
}
public static void f2() {
        f2();
}
```

## throw

可以在指定的方法中抛出异常

### 使用格式

```java
throw new XXXEeception("异常产生的原因");
```

* throw关键字必须要写在**方法内部**

* throw关键字后面的new的对象必须要**Exception** 或者他的**子类对象**

* throw关键字抛出指定的异常对象，我们必须要处理这个异常对象

> throw关键字后面创建的RuntineExceotion或者其子类对象，可以不处理，默认交给JVM处理​

* 如果**throw关键字** 后面创建的是**编译异常** ，那么我们必须要处理这个异常，使用`throws`或者`try...catch`

```java
public class ExceptionDeal {
    public static void main(String[] args) {
        int[] arr1 = null;
        int[] arr2={12,34};
        getEle(arr1,1);
        getEle(arr2,2);
    }

    public static int getEle(int[] arr, int index) {
        if (arr == null) {
            throw new NullPointerException("传递的数组为空!");
        }
        if (index < 0 && index > arr.length - 1) {
            throw new ArrayIndexOutOfBoundsException("数组下标越界!");
        }
        return arr[index];
    }
}
```

![image-20211124230307122](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124230307122.png)

## try...catch

类似if() {} else{}

### 使用格式

```java
try{
    //可能出现异常的部分
}catch(可能异常类型名称 异常类型对象){
    //异常处理代码
}catch(异常类型 对象名称){
  //异常处理代码
}
...
```

### 注意

1. try中可能会抛出多个异常对象，那么就要使用多个catch来处理异常对象

2. 如果try中产生了异常，那么就会执行catch中的异常逻辑，执行完毕catch中的异常逻辑，**会继续执行** `try. . catch`**后面的代码** ，如果try中没有异常，那么不会执行catch中的异常处理逻辑，执行完try中语句后直接执行try. . catch后面的代码

```java
public class ExceptionDemo4 {
    public static void main(String[] args) {
        try {
            int a = 10;
            System.out.println(a / 0);
        } catch (Exception e) {
            if (true){
                System.out.println(777);
            }
            e.printStackTrace(); // 打印异常
            System.out.println(e.getMessage()); // 打印异常发生的原因
            System.out.println(e.toString());
        }

        System.out.println(111);
        System.out.println(111);
        System.out.println(111);
    }
}
```

![image-20211124230319019](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124230319019.png)

## finally块

### 使用格式

```java
try{
    //可能出现异常
}catch(异常类型 对象名称){
    
}catch(){

}
finally{
  //无论如何都会执行
}
```

### 注意

1. `finally`**不能单独使用** ，必须和try...catch一起使用

2. finally一般用来**释放资源** （资源的回收），无论程序是否出现异常，最后资源都要释放（IO流中使用较多）

```java
public class ExceptionDemo5 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File("Demo2.java"));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("释放资源");
//            fis.close();
        }

    }
}
```

![image-20211124230330018](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124230330018.png)

## throws

交给别人处理异常

### 作用

当方法内部抛出异常对象，我们必须要对异常进行处理
可以使用throws处理异常，会把异常对象声明抛出给方法的调用者处理（自己不处理，给别人处理）；如果都没人处理交给JVM

### 使用格式

```java
修饰符 返回值类型 方法名称() throws AAAException,BBBException{
    throw new AAAException();
    throw new BBBException();
}
```

### 注意

1. throws关键字**必须写在方法声明处**

2. throws关键字后面声明的异常必须是Exception或其子类

3. 方法内部如果抛出了多个异常，那么throws后面必须也要声明对应的异常

    1. 如果抛出的异常对象有**父子关系** ，那么**直接抛出父异常** 即可

4. **调用一个声明抛出异常的方法** ，我们必须处理声明的异常

    1. 继续使用`throws`抛出

    2. 使用`try...catch`处理

```java
//子类重写父类 类中抛出的异常等级要小于等于父类中抛出的异常等级
public class Person {

    public void show01() throws NullPointerException{}

    public void show02() throws IndexOutOfBoundsException{}

    public void show03() throws IndexOutOfBoundsException{}

    public void show04() throws RuntimeException{}
}
class Student extends Person{
    //子类重写方法时抛出异常 等于 父类的异常
    @Override
    public void show01() throws NullPointerException{}

    //子类重写方法时抛出的异常 大于 父类的异常
    @Override
    public void show02() throws RuntimeException {}

    //子类重写方法时，不抛出异常
    @Override
    public void show03() {}

    //子类重写方法时抛出的异常 小于 父类的异常
    @Override
    public void show04() throws IndexOutOfBoundsException{}
}
```

**重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。**

例如：
父类的一个方法申明了一个检查异常IOException，在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常，可以抛出非检查异常。
`Error和RuntimeException及其子类成为未检查异常（unchecked），其它异常成为已检查异常（checked）`

**子类重写父类方法抛出的异常范围**

<https://www.cnblogs.com/paul011/articles/7850662.html%20>

## 自定义异常

### 格式

```java
public class xxxException extends Exception | RuntimeException {
  //添加空参构造
    //添加一个带有异常信息的构造
}
```

### 注意

1. 命名规则`XXXException `表示为这是一个异常类

2. 要求**继承Exception** 或者**RuntimeException**

    1. 继承Exception：**编译期异常** ，如果方法内部抛出编译期异常，必须对处理，使用throws或者try...catch

    2. 继承RuntimeException：**运行时异常** ，无须处理，直接交给JVM处理

```java
public class RegisterException extends RuntimeException {
    //无参构造
    public RegisterException() {
    }

    //带有消息的有参构造
    public RegisterException(String message) {
        super(message);
    }

}
```

```java
/*
    要求:我们在模拟注册操作时，如果用户名已经存在了，则抛出一个异常:并提示，该用户已经注册

    自定义注册异常
     */

public class RegisterDemo {
        static Set<String> set=new HashSet<>();


    public static void main(String[] args) {
        Collections.addAll(set,"hzz","taf","李小龙");
        try{
            System.out.println("输入用户名：");
            Scanner scanner=new Scanner(System.in);
            String username=scanner.nextLine();
            checkUserName(username);
            System.out.println("注册成功");
        }catch (RegisterException e){
            e.printStackTrace();
        }
    }

    private static boolean checkUserName(String username) {
        if(set.contains(username)){
            throw new RegisterException("用户名已存在");
        }
        return true;
    }
}
```

# try-with-resouces

**JDK1.8 优化**
在try**后面添加了(),在()中定义流对象** ，作用域**只在try块中有效**
try代码执行完毕，会自动把流对象释放掉

```java
try (FileInputStream fis = new FileInputStream(new File("a.txt"));
     FileOutputStream fos = new FileOutputStream(new File("b.txt"));
    )
```

**JDK1.9**
**try的前面可以定义流对象**
在try的（）中可以**直接引入流对象的名称（变量名）**
在try代码执行完毕后，流对象可以释放掉，不用写finally

```java
 FileInputStream fis = new FileInputStream(new File("a.txt"));
        FileOutputStream fos = new FileOutputStream(new File("b.txt"));
        try (fis; fos) 
```

```java
try (BufferedReader br = new BufferedReader(new FileReader(decodePath));
     PrintWriter writer = new PrintWriter(new File(writePath))) {
    String str = null;
    while ((str =br.readLine()) != null) {
        writer.print(str);
    }
} catch (IOException e) {
    e.printStackTrace();
}

```

如果想释放自定义资源的话，只要让它实现` AutoCloseable 接口`，并提供 `close() `方法即可

```java
public class TrywithresourcesCustom {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource();) {
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("关闭自定义资源");
    }
}

```

# 异常最佳实践

**1）尽量不要捕获 RuntimeException**

**2）尽量使用 try-with-resource 来关闭资源**

**3）不要捕获 Throwable**

**4）不要省略异常信息的记录**

```java
public void logAnException() {
    try {
    } catch (NumberFormatException e) {
        log.error("哦，错误竟然发生了: " + e);
    }
}
```

**5）不要记录了异常又抛出了异常** **6）不要在 finally 块中使用 return**

```java
try 块中的 return 语句执行成功后，并不会马上返回，而是继续执行 finally 块中的语句，如果 finally 块中也存在 return 语句，那么 try 块中的 return 就将被覆盖。
```
