当前位置: 首页 > news >正文

JavaSE丨异常处理详解,高效应对程序中的“意外”

一、异常

1.1 概述

        程序在运行过程中,由于意外情况导致程序发生异常事件,默认情况下发生的异常会中断程序的运行

        在Java中,把常见的异常情况,都抽象成了对应的异常类型,那么每种异常类型都代表了一种特定的异常情况。

        当程序中出现一种异常情况时,也会创建并抛出一个异常类型对象,这个对象就表示当前程序所出现的问题。

如图:

        例如,程序中有一种异常情况是,当前使用下标从数组中取值的时候,这个下标值超过了数组下标的最大值,那么程序中就出现了异常情况,java中把这种异常情况抽象成了一个类: java.lang.ArrayIndexOutOfBoundsException ,表示程序中出现了数组下标超过边界的异常情况。

案例展示:

观察下面各种异常情况:

//如何理解异常:
// 程序不正常情况,统称为 异常
public class Test_Basic {public static void main(String[] args) {// ArithmeticExceptionint a = 10 / 0;String s = "abc";//: NumberFormatExceptionint n = Integer.parseInt(s);Object obj = new Object();//new String("hello");//类型转换异常:ClassCastExceptions = (String)obj;int[] arr = {1,2,3,4};arr = null;//空指针异常:NullPointerExceptionSystem.out.println(arr[0]);//数组索引越界 ArrayIndexOutOfBoundsExceptionSystem.out.println(arr[4]);}
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zeroat Test_Basic.main(Test_Basic.java:6)

        可以看出,当前程序出现异常情况时,会创建并抛出和该异常情况对应的异常类的对象,这个异常对象中保存了一些信息,用来表示当前程序到底发生了什么异常情况。

        通过异常信息,我们可以定位异常发生的位置,以及异常发生的原因

1.2 异常体系

        异常体系中的根类是: java.lang.Throwable ,该类下面有两个子类型, java.lang.Errorjava.lang.Exception

        注意:Throwable 表示可以被抛出的

  • Error ,表示错误情况,一般是程序中出现了比较严重的问题,并且程序自身并无法进行处理。
  • Exception ,表示异常情况,程序中出了这种异常,大多是可以通过特定的方式进行处理和纠正的,并且处理完了之后,程序还可以继续往下正常运行

1.3 异常种类

        我们平时使用的异常类型,都是两种:

  •  编译时异常
  •  运行时异常
编译时异常:
  • 继承自 Exception 类的子类型,也称为checked exception
  • 编译器在编译期间,会主动检查这种异常,如果发现异常则必须显示处理, 否则程序就会发生错误,无法通过编译
运行时异常 :
  • RuntimeException 类及其子类,也称为unchecked exception
  • 编译器在编译期间,不会检查这种异常,也不要求我们去处理,但是在运行期间,如果出现这种异常则自动抛出

1.4 异常传播

        如果一个方法中出现了异常的情况,系统默认的处理方式是:自动创建异常对象,并将这个异常对象抛给当前方法的调用者,并一直向上抛出,最终传递给 JVM,JVM默认处理步骤有2步:

  1. 把异常的名称,错误原因及异常出现的位置等信息输出在了控制台
  2. 程序停止执行
案例展示:
public class Test_Default {public static void main(String[] args) {System.out.println("hello");test1();System.out.println("world");}public static void test1() {test2();}public static void test2() {test3();}public static void test3() {//下面代码会抛出异常int a = 1 / 0;}
}

运行结果:

hello
Exception in thread "main" java.lang.ArithmeticException: / by zeroat Test_Default.test3(Test_Default.java:18)at Test_Default.test2(Test_Default.java:13)at Test_Default.test1(Test_Default.java:9)at Test_Default.main(Test_Default.java:4)
代码执行步骤解析:
  1. 因为 java.lang.ArithmeticException 是运行时异常,所以代码可以编译通过
  2. 程序运行时,先输出"hello",然后一层一层调用,最终执行test3方法
  3. 执行test3方法时,出现除数为0的情况,系统自动抛出异常
  4. java.lang.ArithmeticException 代码中没有对异常进行任何捕获处理,所以该异常往上传递给test2 --> test1 --> main --> JVM
  5. JVM虚拟机拿到异常后,输出异常相关信息,然后终止程序

二、异常抛出

2.1 自动抛出

        Java代码中,出现了提前指定好的异常情况的时候,代码会自动创建异常对象, 并且将该异常对象抛出。

        例如,上述案例中执行 int a = 1/0; 的时候,代码会自动创建并抛出 ArithmeticException 类型的异常对象,来表示当前的这种异常情况。(算术异常)

        又如,代码中执行自动创建并抛出 String str = null; str.toString(); 的时候,代码会 NullPointerException 类型的异常对象,来表示当前这种异常情况。(空指针异常)

2.2 手动抛出

        以上描述的异常情况,都是JVM中提前规定好的,我们不需要干预,JVM内部自己就会创建并抛出异常对象。

        但是在其他的一些情况下,我们也可以手动的创建并抛出异常对象,抛出后系统也会按照默认的方式去处理

        手动抛出异常固定格式: throw 异常对象;

案例展示:
public class AgeValidator {// 验证年龄的方法public static void checkAge(int age) {// 规定年龄必须在0-150之间,否则视为无效if (age < 0 || age > 150) {// 手动创建并抛出异常对象throw new IllegalArgumentException("年龄必须在0到150之间,当前值:" + age);}System.out.println("年龄验证通过:" + age);}public static void main(String[] args) {// 测试正常情况checkAge(25); // 年龄有效,会输出验证通过信息// 测试异常情况checkAge(-5); // 年龄无效,会触发手动抛出的异常}
}

三、异常处理

代码中出现了异常,除了默认的处理方式外,我们还可以手动处理异常

  • 声明继续抛出异常,借助throws关键字实现
  • 捕获并处理异常,借助try、catch、finally关键字实现

3.1 throws

        throws关键字用于在方法声明中指定该方法可能抛出的异常类型

        这个声明的目的,就是告诉方法的调用者,调用这个方法的时候要小心 ,方法在运行的时候可能会抛出指定类型的异常。

案例展示:

多个异常声明

import java.io.FileInputStream;
import java.io.IOException;public class ThrowsExample {// 声明该方法可能会抛出IOException和NullPointerException异常public static void processFile(String filePath) throws IOException, NullPointerException {if (filePath == null) {throw new NullPointerException("文件路径不能为null");}FileInputStream fis = new FileInputStream(filePath);// 其他文件处理逻辑fis.close();}public static void main(String[] args) {String filePath = null;try {processFile(filePath);} catch (IOException | NullPointerException e) {System.out.println("发生异常: " + e.getMessage());}}
}
我们将throw与throws进行一个对比:
对比维度throw 关键字throws 关键字
作用手动抛出一个具体的异常对象(触发异常)声明方法可能会抛出的异常类型(告知风险)
使用位置方法体内部(用于执行具体的异常抛出动作)方法签名末尾(用于声明异常,格式:方法名() throws 异常类型
抛出内容必须是异常对象(如 throw new Exception()必须是异常类型(如 throws IOException
数量限制一次只能抛出一个异常对象可以声明多个异常类型,用逗号分隔(如 throws AException, BException
处理要求抛出异常后,要么用 try-catch 处理,要么用 throws 声明抛给上层声明异常后,调用者必须处理(try-catch)或继续用 throws 向上声明
适用场景主动触发异常(如业务规则校验失败时)告知方法调用者 “此方法可能会引发这些异常,需注意处理”
总结
  • throw 是 “主动扔出一个具体的异常”(执行动作);
  • throws 是 “提前声明方法可能会扔出哪些异常”(告知风险)。

3.2 try-catch

        try-catch 语句块,就是用来对指定代码,进行异常捕获处理,并且处理完成后,JVM不会停止运行,代码还可以正常的往下运行。

捕获异常语法:
 try {可能会出现异常的代码;}catch(异常类型 引用名) {//处理异常的代码,可以是简单的输出异常信息//也可以使用日志进行了记录,也可以对数据进行修改纠正等操作//一般输出异常信息//e.printStackTrace();
}

try:该代码块中包含可能产生异常的代码

catch:用来进行某种类型异常的捕获,并对捕获到的异常进行处理

执行流程:
  1. 程序从 try 里面的代码开始执行
  2. 出现异常,就会跳转到对应的 catch块 里面去执行
  3. 执行完毕之后,程序出 catch块,继续往下执行
案例展示:

模拟用户输入数字并进行除法运算的场景,处理可能出现的输入格式错误和除零异常

import java.util.Scanner;public class DivisionCalculator {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("===== 除法计算器 =====");try {// 尝试获取用户输入的被除数System.out.print("请输入被除数(整数):");String num1Str = scanner.nextLine();int num1 = Integer.parseInt(num1Str);  // 可能抛出NumberFormatException// 尝试获取用户输入的除数System.out.print("请输入除数(整数):");String num2Str = scanner.nextLine();int num2 = Integer.parseInt(num2Str);  // 可能抛出NumberFormatException// 尝试执行除法运算int result = num1 / num2;  // 可能抛出ArithmeticException(除数为0时)// 如果以上步骤都没有异常,输出计算结果System.out.println("计算结果:" + num1 + " ÷ " + num2 + " = " + result);} catch (NumberFormatException e) {// 处理输入格式错误(用户输入的不是整数)System.out.println("错误:请输入有效的整数!" + e.getMessage());} catch (ArithmeticException e) {// 处理除法异常(除数为0)System.out.println("错误:" + e.getMessage() + ",除数不能为0!");}// 无论是否发生异常,都会执行以下代码System.out.println("\n程序执行结束,感谢使用!");scanner.close();}
}

上述案例中体现出,如果try语句块中的代码可能抛出多种异常,并且是不同类型的,则可以写多个 catch语句块,用来同时捕获多种类型异常。

注意事项:

这种异常处理方式,要求多个catch中的异常不能相同

如果catch中的多个异常类之间有子父类关系的话,那么子类异常必须写在父类异常上面的catch块中,父类异常必须写在下面的catch块中。 因为如果父类型异常再最上面的话,下面catch语句代码,永远不会被执行!

3.3 finally 语句 

        finally 关键字可以和 try、catch关键字一起使用,固定搭配为: try catch-finally ,它可以保证指定finally中的代码一定会执行,无论是否发生异常!

finally 块的主要作用:
  • 资源释放:在 try 块中打开的资源(例如文件、数据库连接、网络连接等) 可以在 finally 块中关闭或释放,以确保资源的正确释放,即使在发生异常的情况下也能够执行释放操作。
  • 清理操作: finally 块可以用于执行一些清理操作,例如关闭打开的流、释放锁、取消注册监听器等。
  • 异常处理的补充:finally块可以用于在try块和catch块之后执行一些必要的操作,例如记录日志、发送通知等。
案例展示:
import java.io.FileInputStream;
import java.io.IOException;public class FileReaderExample {public static void main(String[] args) {FileInputStream fis = null; // 声明文件输入流变量try {// 尝试打开文件并读取内容fis = new FileInputStream("data.txt");System.out.println("文件打开成功,准备读取...");// 模拟读取过程中可能发生的异常(例如文件内容异常)int data = fis.read();if (data == -1) {throw new IOException("文件内容为空");}System.out.println("文件读取完成,内容:" + (char) data);} catch (IOException e) {// 处理文件操作相关异常System.out.println("文件操作出错:" + e.getMessage());} finally {// 无论是否发生异常,都必须关闭文件流(释放资源)System.out.println("进入finally块,准备关闭文件流...");if (fis != null) { // 确保流对象已初始化try {fis.close(); // 关闭流可能也会抛出IOExceptionSystem.out.println("文件流已成功关闭");} catch (IOException e) {System.out.println("关闭文件流时出错:" + e.getMessage());}}}System.out.println("程序执行结束");}
}

四、自定义异常

如果要自定义一个编译时异常类型,就自定义一个类,并继承 Exceptionn

如果要自定义一个运行时异常类型,就自定义一个类,并继承 RuntimeException

自定义异常步骤:

无论哪种类型的自定义异常,定义步骤基本一致,核心是 “继承父类 + 提供构造方法”

  1. 定义异常类
    类名通常以 Exception 结尾(如 InvalidAgeExceptionPasswordErrorException),直观体现异常含义。

  2. 指定继承关系

    • 编译时异常:public class 自定义类名 extends Exception { ... }
    • 运行时异常:public class 自定义类名 extends RuntimeException { ... }
  3. 提供构造方法
    必须至少包含两种构造方法(通过 super() 调用父类构造):

    • 空参构造:用于创建无详细信息的异常对象。
    • 带参构造:接收一个 String 类型的异常信息(描述错误原因),传给父类保存(便于通过 getMessage() 获取)。
案例展示:
// 自定义编译时异常
public class InvalidAgeException extends Exception {// 空参构造public InvalidAgeException() {super(); // 调用父类Exception的空参构造}// 带异常信息的构造public InvalidAgeException(String message) {super(message); // 调用父类Exception的带参构造,保存异常信息}
}// 自定义运行时异常
public class EmptyNameException extends RuntimeException {public EmptyNameException() {super();}public EmptyNameException(String message) {super(message);}
}
http://www.xdnf.cn/news/1433071.html

相关文章:

  • 结构抗震与土木工程研究
  • Windows控制台颜色修改
  • 移动端网页设计vm+rem,和px实现方式对比
  • 【设计模式】三大原则 单一职责原则、开放-封闭原则、依赖倒转原则
  • Javascript》》JS》》ES6》 Map、Set、WeakSet、WeakMap
  • 【MATLAB绘图进阶】(3.1)从基础到高级的图形样式控制
  • Android14 init.rc各个阶段的主要操作详解2
  • gbase8s之导出mysql导入gbase8s
  • 良策金宝AI:电力工程的“最强大脑”,如何重塑设计新范式?
  • css中的v-bind 动态变化
  • 技术架构设计--资源与链接、安全灾备
  • Android URC 消息透传 MTK 代码方案
  • T40N君正/INGENIC专业嵌入式CPU计算能力,集成XBurst2双核处理器(1.2GHz)、RISC-V协处理器和神经网络加速器(2TOPS算力)
  • 防止应用调试分析IP被扫描加固实战教程
  • 宋红康 JVM 笔记 Day11|直接内存
  • 爬虫基础学习 - Beautifulsoup
  • 电子电子技术知识------MOSFET管
  • 高校党建信息管理系统的设计与实现-(源码+LW+可部署)
  • 实验4-HTTP协议的运行过程
  • 【大前端】Vue 和 React 主要区别
  • React 中 key 的作用
  • C#---共享项目
  • 解决戴尔笔记本电脑键盘按键部分失灵
  • python 创建websocket教程
  • 从自动化到智能化:家具厂智能化产线需求与解决方案解析
  • Qt内存映射到文件,解决打开大文件占用内存高的问题
  • STM32-FreeRTOS操作系统-任务管理
  • Linux - 进程切换 进程调渡
  • 【Linux】进程信号
  • 第2.7节:多模态大模型之Midjourney