四十二、异常


1、概述

异常就是程序出现了不正常的情况。程序在执行过程中,数据导致程序不正常,最终会导致JVM的非正常停止。

注意:语句错误不算在异常体系中。

异常的存在形式:

异常有类型之分,比如我们之前有接触过的比较熟悉的 数组越界异常、空指针异常、类型转换异常。当程序中产生异常时,其实就是在异常的位置创建了一个该异常的对象,该对象携带了相关的异常信息。因此,异常就是 Java 中提供的类的对象

程序中异常产生后,是如何处理的:

程序中一旦产生异常, 首先会中断向下执行。异常的传递要根据处理方式(而定,如果没有处理,默认是将异常传递给本方法的调用者。不断往回传递,直到JVM收到该异常,此时程序终止执行。

2、异常体系

Throwable
    Error: 		严重问题,通过代码无法处理。比如:内存溢出
    
    Exception:	称为异常类,它表示程序本身可以处理的问题
    	RuntimeException及其子类:	 运行时异常。(空指针异常,数组索引越界异常)
        非RuntimeException及其子类:	编译器必须处理的,否则程序不能通过编译。(日期格式化异常)    

3、JVM处理异常

如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理。那jvm是如何处理的?

  • 把异常的类型,原因,位置打印在控制台
  • 程序停止执行

注意:程序中出现了异常,会在当前位置创建此异常的对象,对象中包含了异常的信息,并把此异常交给本方法的调用者处理。

4、声明异常——throws

格式:

【格式】
修饰符 返回值类型 方法名(参数列表) throws 异常类型1 , 异常类型2...{...}

【举例】
public void show(int i) throws NullPointerException,IOException{...}     

作用:

  • 表示告知调用者当前的方法可能会出现某些异常,使用是需要注意。
  • 如果当前方法没有出现任何异常,那么代码会正常执行。
  • 如果当前方法中出现了异常,会把异常交给本方法调用者处理(甩锅)。

注意事项:

  • 编译时异常因为在编译时就会检查,所以必须要写在方法后面进行显示声明。
  • 运行时异常因为在运行时才会发生,所以在方法后面可以不写。
  • 如果声明多个异常有子父类关系,那么只要声明一个父类即可(多态)。

5、抛出异常——throw

思考: 以前出现了异常,虚拟机帮我们创建一个异常对象,抛给调用者。但是如果我们需要自己手动创建一个异常对象该如何写?

5.1 格式

修饰符 返回值类型 方法名(参数列表){
    throw new 异常对象();
}

注意:

  1. 抛出异常的格式必须在方法的内部完成

抛出异常的意义所在

  1. 在方法中,当传递的参数有误,没有继续运行下去的意义了,则采取抛出处理,表示让该方法结束运行。
  2. 告诉调用者方法中出现了问题。

6、throws与throw区别

throws:

  • 用在方法声明后面,跟的是异常类名
  • 表示声明异常,调用该方法有可能会出现这样的异常

throw:

  • 用在方法体内,跟的是异常对象名
  • 表示手动抛出异常对象,告知调用者数据传入有误

7、捕获异常try-catch

7.1 try-catch介绍

之前的声明或者抛出都是将异常传递出去,让调用者知道异常信息

而捕获处理是本方法内部进行处理,能够阻止异常的传递,从而保证程序能够继续往下执行

7.2 捕获异常格式

try{
    try中存放可能会出现问题的代码
    代码...
}catch(异常类型){
    处理异常的方案
}
其他代码

7.3 捕获异常执行方式

  • 如果try中没有遇到问题:

    从上往下依次执行,catch中不执行

  • 如果 try 中代码遇到了问题:

    下面的代码不会执行,会拿当前异常对象和异常类型匹配,匹配成功执行处理异常代码

  • 如果出现的问题没有被捕获:

    如果异常没有捕获到。虚拟机会帮助我们处理

  • 处理多个异常

    1. 多个异常,每个异常单独处理

      try{
          异常1
      }catch(异常1){}
      
      try{
          异常2
      }catch(异常2){}
      
    2. 多个异常,一次捕获,多次处理

      注意:如果多个异常有继承关系,父类异常要放在子类异常后面。建议使用因为不用异常以后可能有不同处理的方案

      try{
          异常1 
          异常2
      }catch(异常1){
      }catch(异常2){
      }
      
    3. 多个异常,异常一次捕获,一次处理

      try{
          异常1
          异常2
      }catch(Exception e){
      }
      

8、常用方法

Throwable的成员方法

方法名 说明
public String getMessage() 返回 详细信息原因
public String toString() 返回 类名 + 原因
public void printStackTrace() 把异常的错误信息输出在控制台
打印类名 + 原因 + 位置

9、finally

9.1 关键字介绍

finally关键字,表示最终要执行的,不能单独使用,需要结合try-catch使用。

通常用于一些资源的释放,比如后续学到的IO流知识,流资源的释放

9.2 格式

try {
    1 xxxxxxx
    2 xxxx异常
    //3 释放资源(当有异常时不会执行)
} catch (异常) {
} finally {//一定会执行
	//释放资源的代码 对象.close();
}

分析:

  • 如果3行代码进行释放资源,在2行代码产生异常时将无法执行。
  • 如果将释放资源的代码放到finally代码块中,就能保证一定能执行。除非 JVM 结束执行(比如之前调用的 System.exit(0))。

10、自定义异常

10.1 概述

在JDK中的异常类型,不满足实际的业务需求时,就可以自己定义异常。自定义异常存在的意义:就是为了让控制台的报错信息更加的见名之意。

10.2 实现步骤

public class AgeOutOfBoundsException extends RuntimeException{
    public AgeOutOfBoundsException(){
        
    }
    public AgeOutOfBoundsException(String message){
        super(message);
    }
}

① 定义异常类

② 写继承关系

③ 空参构造

④ 带参构造

10.3 注意

  • 如果要自定义编译时异常,就继承Exception。
  • 如果要自定义运行时异常,就继承RuntimeException。

11、小结

异常出现分两种:① 系统出现的异常。② 手动制造出来的异常 throw

异常分两种:① 编译时异常。 ② 运行时异常。

解决异常分:①throws 抛出,抛给父类。 ② 捕获,使用 try-catch

throws 如果都不进行异常处理,最终抛给JVM虚拟机。所以系统会强制停止。它的存在只是说明有可能出现异常。

try-catch 当前出现的异常,当前解决。捕捉到异常就在终端上进行显示, 不影响下面代码的执行

编译时异常 必须要进行解决,如果不解决,则不让执行。可以抛出,也可以捕获

运行时异常 在运行时才会报错,可以不选择进行异常处理