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

Java的异常机制

简介

异常:异常并不是指语法错误,而是指程序运行过程中可能出现的错误,有一部分错误是在编写代码时就可以预料到有可能出现的,所以在编译时必须处理,例如,要读取一个外部文件,就有可能遇到文件不存在的情况,在代码中就需要处理这种情况,文件不存在,就是一个可能出现的异常,在Java中就是FileNotFoundException。

异常机制:异常机制就是当程序出现错误,程序安全退出的机制。异常处理是衡量一门语言是否成熟的标准之一

异常处理机制的好处:增加Java语言的健壮性;把异常流程的代码和正常业务代码分开,让程序更简洁。

异常的处理方式:Java 通过面向对象的方法来处理异常,具体分为两个步骤

  • 抛出异常:在一个方法的运行过程中,如果发生了异常,则这个方法会产生代表该异常的一个对象,并把它交给运行时的系统,这个动作称为抛出异常。
  • 捕获异常:运行时系统在方法的调用栈中查找,直到找到能够处理该类型异常的代码,这一个过程称为捕获(catch)异常,如果找不到,就把异常交给虚拟机处理。

基本使用

异常分类

编译期异常:由外部错误引起的异常。正确的程序在运行中,很容易出现的、情理可容的异常状况,在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。Java编译器会检查这种异常,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过,所以它又称受检查的异常

运行期异常:由程序内部错误引起的异常。在编译期不会检查这些异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

JVM产生的异常:这种异常Java程序无法处理,只能打印异常信息并退出程序

异常类的体系

Throwable类:所有异常类型的祖先类,只有继承了 Throwable 的类才能被 throw 或 catch

Error类:Throwable类的直接子类,通常表示jvm异常,如堆栈溢出,

Exception类:Throwable类的直接子类,用于Java程序可能出现的异常情况,如果用户要创建自定义异常类,也是继承Exception类。Exception 下又分为运行时异常和非运行时异常。

  • RuntimeException:运行时异常类,在程序运行过程中,由程序本身的错误引起的异常,如NullPointerException、IndexOutOfBoundsException,在编译期不会检查这些异常。一个异常类如果继承自RuntimeException,在方法中抛出后不需要在方法签名上进行声明,
  • 非运行时异常:指的是RuntimeException类及其子类以外的异常,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能通过编译。

相关源码:除了核心父类Throwable,存储了异常的堆栈信息,其它相关子类都比较简单,只是调用父类的构造方法,传入自己独有的异常描述信息,

处理异常的语法

异常处理涉及到的关键字:

  • try:可能出现异常的语句,放在try块中
  • catch:捕获并处理异常,
  • finally:finally块中的语句是在任何情况下都必须执行的代码,除非jvm退出,
  • throw:在方法体中抛出异常实例,
  • throws:在方法签名上声明可能会出现的异常
  • assert:断言。严格来讲assert应该是用在单元测试中的,正常代码中如果想要assert关键字生效,需要在程序启动时特殊配置,鉴于有时候会在代码中遇到通过assert来进行断言,处理异常情况的案例,这里做个介绍,但是正常代码中不应该出现assert关键字

抛出异常 throws、throw

throws:当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法外部进行处理,使用 throws 在方法声明处声明一个异常。

使用 throws 声明异常的思路是:

  • 当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;
  • 如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。
  • JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是程序在遇到异常后自动结束的原因。

throw 抛出异常:语法 throw new ExceptionType,在方法体中抛出一个异常对象,只有继承了RuntimeException的类才可以被throw抛出。

案例:throw和throws,在下面的程序中,要求用户在运行程序时,向程序传入一个参数,程序会把这个参数作为一个文件名来解析,如果用户没有传入参数,那么就抛出一个运行时异常,如果根据文件名无法找到文件,程序不会处理这个异常,而是会按照默认方式抛给虚拟机。

public class Test3 {public static void main(String[] args) throws FileNotFoundException {if (args.length == 0) {throw new RuntimeException("请输入文件名");}String fileName = args[0];FileInputStream fileInputStream = new FileInputStream(fileName);}
}

这里演示了throw和throws的基本使用,throw,用于抛出一个运行时异常,在本案例中,程序启动时,用户可能输入了参数,也可能没有输入参数,如果没有输入参数,程序无法继续,就要抛出一个运行时异常,提醒用户,然后终止程序。throws,用于在方法上声明一个编译时异常,在本案例中,用户输入的文件名, 根据它可能会找到一个文件也可能不会,这是编译时可以预料到的,所以是一个编译时异常,如果无法处理,需要在方法上声明该异常。

这里详细解释一下,为什么在这里没有输入参数属于运行时异常:因为这属于程序使用逻辑错误,是开发者或用户违反了程序的预期使用方式,程序的设计意图是 “必须传入文件名参数才能运行”,如果没有传入,本质上是 “调用方式错误”,属于开发者或用户的疏漏。这类错误理论上是可以提前避免的,比如在文档中明确要求传入参数,或者在测试阶段覆盖这种情况,因此Java不强制要求编译时处理,而是归类为运行时异常,由开发者在逻辑上保证避免。

为什么 “文件不存在” 是编译时异常?文件不存在会抛出编译时异常,因为这属于程序外部环境导致的可预见情况,与程序逻辑无关,且无法通过代码完全避免,即使开发者严格要求传入文件名,也无法保证用户输入的文件一定存在,可能用户输错文件名、文件被删除、路径错误等,这是程序运行时依赖的外部资源的状态决定的,属于不可控但可预见的场景。Java 设计编译时异常的目的就是强制开发者提前考虑并处理这类情况,比如提示用户 “文件不存在,请检查路径”,否则程序可能在运行时毫无征兆地崩溃。因此编译器会强制要求处理。

总结:编译时异常和运行时异常的区别,运行时异常是程序内部的逻辑错误,可以避免,编译时异常是程序外部的错误,在程序内无法避免,需要在编译时考虑这种情况。

捕获异常 try catch finally

上面的代码演示了如何抛出异常,抛出异常是交给虚拟机处理,用户也可以自己处理这些异常,把异常信息进行友好输出,非致命异常,可以吞掉,让程序继续进行。使用try、catch、finally来实现异常处理。

格式:

try{// 可能发生异常的语句,try 块中声明的变量仅仅是局部变量,外部无法访问
} catch(ExceptionType | ExceptionType2 [, ....] e) {// 处理异常语句
}[.....
][finally{// 无论有没有异常都会执行的语句。
}]

在 try 块后可以跟多个 catch 块,每一个 catch 块所对应的异常范围应该是越来越大。

执行顺序:

  • 如果在catch块中抛出异常:先执行finally中的语句,然后再抛异常
  • 在 finally 块中抛出的任何异常都会覆盖掉在其前面由 try 或者 catch 块抛出异常。包含 return 语句的情形相似。鉴于此,除必要情况下,应该尽量避免在 finally 块中抛异常或者包含 return 语句。

案例1:继续上一个案例,使用try catch处理编译时异常,捕获异常,输出对用户友好的异常信息,然后程序继续或退出。

public class Test4 {public static void main(String[] args) {if (args.length == 0) {throw new RuntimeException("请输入文件名");}String fileName = args[0];try {FileInputStream fileInputStream = new FileInputStream(fileName);} catch (FileNotFoundException e) {System.out.println("文件 " + fileName +  " 不存在,程序退出");}}
}

案例2:finally的使用。网络编程中的一个案例,在finally块中关闭socket、输入流等资源

public static void processBusiness(Socket socket) {InputStream inputStream = null;try {// 业务处理,读取输入inputStream = socket.getInputStream();int len;while ((len = inputStream.read(bytes)) != -1) {String input = new String(bytes, 0, len);System.out.println("[" +Thread.currentThread().getName()+ "]客户端发来的数据:"+ input);}} catch (IOException e) {e.printStackTrace();} finally {// 关闭资源if (inputStream != null){try {socket.shutdownInput();inputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}}
}

案例3:多个catch块

public static Long convertDateStrToTimestamp(String date) {SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");try {Date dateObj = formatter.parse(date);return dateObj.getTime();} catch (ParseException e) {return 0L;} catch (Exception e) {return -1L;}
}

从上往下,异常范围应该越来越大。

案例4:在一个catch块中捕获多个异常

public static Long convertDateStrToTimestamp(String date) {SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");try {FileInputStream fileInputStream = new FileInputStream(date);Date dateObj = formatter.parse(date);return dateObj.getTime();} catch (ParseException | FileNotFoundException e) {return 0L;} catch (Exception e) {return -1L;}
}

如果一个try块中涉及到多个编译时异常,可以把采用这种写法,但是通常不推荐。

案例5:在finally块中return返回值。

public class Test5 {public static void main(String[] args) {Long l = convertDateStrToTimestamp("2025-02-28");System.out.println("l = " + l);}public static Long convertDateStrToTimestamp(String date) {SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");try {Date dateObj = formatter.parse(date);return dateObj.getTime();} catch (ParseException e) {return 0L;} finally {return -1L;}}
}

结果:finally块中的return会覆盖掉正确的返回值,所以尽量不要在finally中return返回值

assert 断言

断言:assert,如果希望在不满足某些条件时阻止代码的执行,就可以考虑用断言来阻止它。断言在软件开发中是一种常用的调试方式,断言用于保证程序最基本、关键的正确性。

断言语法:assert <boolean expression> [: "<message>"]

java 1.4 增加了‘断言’的特性,断言是为了调试程序,并不是发布程序的一部分,默认情况下 jvm 是关闭断言的,如果想要使用断言调试程序,需要手动打开断言程序

不要在正式代码中使用断言,只在单元测试中使用,如果需要启用,程序运行时添加虚拟机参数 -ea

案例:这是在junit框架中使用的

@Test
public void test2() {Properties properties = System.getProperties();assert properties != null;
}

如果properties为null,抛出断言异常

java.lang.AssertionErrorat org.wyj.LearnAssertTest.test2(LearnAssertTest.java:28)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)at org.junit.runners.ParentRunner.run(ParentRunner.java:363)at org.junit.runner.JUnitCore.run(JUnitCore.java:137)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

自动资源管理

java 7引入了自动资源管理的特性,用于自动关闭实现了AutoCloseable接口的资源,无需手动在finally块中关闭,这大大简化了资源管理的代码,并减少了资源泄露的风险。

格式:

try (声明或初始化资源语句) {// 可能会出现异常的语句
}catch(ExceptionType var){// 异常处理语句
}[....]

内部机制:

  • try 语句中声明的资源被隐式声明为 final,资源的作用局限于 try 语句,可以在 try 语句中声明或初始化多个资源,用 ‘;’ 隔开即可,需要关闭的资源必须实现了AutoClosable 或 Closable 接口。
  • 声明资源变量的括号中,不可以为已有的成员变量赋值
  • 资源按照声明相反的顺序关闭,最后声明的资源最先关闭。

源码:

// AutoCloseable接口
public interface AutoCloseable {void close() throws Exception;
}// Closeable接口
public interface Closeable extends AutoCloseable {public void close() throws IOException;
}

使用案例:

public static void main(String[] args) {// 自动关闭 BufferedReader 和 FileReadertry (FileReader fr = new FileReader("test.txt");BufferedReader br = new BufferedReader(fr)) {String line;while ((line = br.readLine()) != null) {System.out.println(line);}} catch (IOException e) {throw new RuntimeException(e);}// 资源会在 try 块结束后自动关闭
}

传统写法:

public static void main(String[] args) {BufferedReader br = null;try {br = new BufferedReader(new FileReader("test.txt"));// 使用资源...} catch (IOException e) {e.printStackTrace();} finally {if (br != null) {try {br.close(); // 需要嵌套 try-catch} catch (IOException ex) {ex.printStackTrace();}}}
}

自定义异常

在实际开发中,自定义异常可以更加清晰地表达业务,团队通常会针对不同的情况,定义专属的异常类。自定义异常可以更精准地描述业务逻辑中的错误场景,提高代码的可读性和可维护性。

定义不同类型的异常:

  • 自定义一个编译时异常:需要继承于java.lang.Exception
  • 自定义一个运行时异常:需要继承于java.lang.RuntimeException

异常类的编写:在自定义异常中,调用父类的构造方法,传入一个字符串,描述异常信息,同时异常类还可以承载其它的异常信息,例如异常编码等。

案例1:自定义运行时异常,代表一个业务异常,所有的业务异常都使用当前异常类

public class BizException extends RuntimeException {private String code;public BizException() {}public BizException(String message) {super(message);this.code = "1";  // 默认错误码是系统错误}public BizException(String code, String message) {super(message);this.code = code;}public BizException(String code, String message, Throwable cause) {super(message, cause);this.code = code;}public BizException(String code, Throwable cause) {super(cause);this.code = code;}public String toString() {return "code:" + this.code + ", msg:" + this.getMessage();}/*** 业务异常不需要收集线程的整个异常栈信息,重写fillInStackTrace方法,,可以减少业务异常抛出导致的开销*/@Overridepublic synchronized Throwable fillInStackTrace() {return this;}
}

当前异常类中自定义了异常码,不同的异常码代表不同的异常,同时,还会抑制堆栈信息的打印,因为业务异常通常不是系统问题,不需要堆栈信息,但是这种情况下要注意,每个业务异常都必须是独一无二的,否则可能会混淆。

处理异常的原则

为一个基本操作定义一个 try-catch 块:不要将几百行代码放到一个 try-catch 块中,异常只能用于非正常情况,try-catch的存在也会影响性能,尽量缩小try-catch的代码范围;

尽量在循环之外使用try-catch

尽量避免运行时异常的出现,如NullPointerException,提前做非空判断

如果方法需要被上层API调用,方法中的编译时异常最好抛出,如果方法是最外层的方法,必须处理异常

明确异常的级别,异常是否需要阻塞主流程,例如,用户在页面修改数据,后端要做两个改动,一是保存数据库,二是记录日志,如果保存数据库时发生异常,那么必须阻塞流程,向用户抛出友好的异常信息,如果是记录日志发生异常,可以不用阻塞流程,而是选择把异常吞掉,仅仅打印异常日志,然后查看日志来解决问题即可。这就是异常的级别,异常是主流程相关还是和主流程无关,是否需要阻塞主流程。

需要为异常提供说明文档:可以参考Java doc,如果自定义了异常或某一个方法抛出了异常,应该在文档注释中详细说明;

常见的异常类

编译时异常:

  • IOException :IO异常
  • ParseException:日期解析失败等
  • ClassNotFoundException:类不存在

运行时异常:

  • NullPointerException:空指针异常,对空对象进行除了赋值以外的任何操作都会报空指针异常,比如调用空对象的方法,对空对象中的字段进行赋值
  • ArrayIndexOutOfBoundsException:数组的索引越界异常,操作数组时使用的索引超出了数组的数据范围会出现;
  • NumberFormatException:数字格式化异常,把非数字的数据类型转换为数字类型时使用了非法的转换对象;
  • IllegalArgumentException:传入的参数不正确
  • ArithmeticException:算术异常
  • ClassCastException:类转换异常
  • IllegalArgumentException:非法参数异常
  • IndexOutOfBoundsException:下标越界异常
  • SecurityException:安全异常

编码过程中的常见告警

  • ‘InputStream’ used without ‘try-with-resources’ statement: 'InputStream’不带’try-with-resources’语句使用,这是编译器的一个警告,对于InputStream的使用应该放在 try with resources 语句中
  • dangling javadoc document:悬空的Java文档。文档注释只有添加到类上或方法上,才可以生成文档,添加到其它地方和普通注释的效果是一样的。当把一个文档注释添加到方法内的时候,就会报这个告警
http://www.dtcms.com/a/327277.html

相关文章:

  • 【牛客刷题】REAL806 放它一马:怪物经验值最大化策略详解
  • 云原生应用的DevOps3(CI/CD十大安全风险、渗透场景)
  • UE5多人MOBA+GAS 42、提高头像画质
  • C++——高性能组件
  • AI大模型基础:BERT、GPT、Vision Transformer(ViT)的原理、实现与应用
  • 【2】Transformers快速入门:统计语言模型是啥?
  • Agent智能体基础
  • 「日拱一码」057 逆向强化学习(IRL)
  • 从0开始的中后台管理系统-5(菜单的路径绑定以及角色页面的实现)
  • 分布式光伏气象站:为光伏电站的 “气象感知眼”
  • 自建知识库,向量数据库 体系建设(一)之BERT 与.NET 4.5.2 的兼容困境:技术代差下的支持壁垒
  • AWS EKS 常用命令大全:从基础管理到高级运维
  • 开发npm包【详细教程】
  • AWS KMS VS AWS Cloud HSM VS AWS Secret Manager?
  • 开源!!! htop移植到OpenHarmony
  • 自动驾驶决策算法 —— 有限状态机 FSM
  • AI项目提示-提示词-属于-mcp-cli等
  • css初学者第五天
  • 【CSS 变量】让你的 CSS “活”起来:深入理解 CSS 自定义属性与主题切换
  • 现代 CSS工具
  • web前端第二次作业
  • 【CSS 视觉】无需JS,纯 CSS 实现酷炫视觉效果(clip-path, filter, backdrop-filter)
  • 微前端面试考点与答案
  • 纯CSS+JS制作抽奖大转盘
  • 【CSS3】录音中。。。
  • aspose word for java 使用书签进行内容填充和更新
  • AppStorageV2:鸿蒙全局状态管理详解-ArkUI本地存储
  • django 如何读取项目根目录下的文件内容
  • Python常用的5种中文分词工具
  • 力扣 hot100 Day71