Java之异常处理
1. 引言
在Java编程中,异常处理是保障程序稳定性和可维护性的关键环节之一。本文将深入探讨Java异常处理的核心概念、用法和规范,帮助开发者更好地应对程序中可能出现的异常情况。
2. 什么是异常
2.1 异常的基本概念
异常是指在程序执行过程中可能出现的意外情况,例如除以零、访问不存在的数组元素等。异常会中断正常的程序流程,需要特殊的处理来恢复程序执行或提供错误信息。
2.2 异常分类
Java中的异常分为两大类:受检异常(Checked Exception
)和非受检异常(Unchecked Exception
)。受检异常是在编译时强制捕获或声明抛出的异常,而非受检异常则是在运行时可能抛出的异常。
3. 异常处理的重要性
3.1 程序的稳定性
良好的异常处理可以确保程序在遇到问题时能够适当地处理,从而减少程序崩溃的风险,提高系统稳定性。
3.2 可读性和维护性
清晰的异常处理代码可以使程序更易读懂、更易维护。异常处理的规范性和一致性有助于团队成员理解代码并快速定位问题。
4. Java中的异常处理
4.1 异常类层次结构
Java异常类继承于Throwable类,分为两大类:Error(表示严重错误,程序无法处理)和Exception(表示可捕获的异常)。Exception下又分为受检异常和非受检异常。
4.2 try-catch块
使用try块来包裹可能抛出异常的代码,然后使用catch块捕获并处理异常。示例代码:
try {
// 可能抛出异常的代码
} catch (特定异常类型 e) {
// 异常处理逻辑
}
4.3 throws关键字
通过在方法签名中使用throws关键字声明可能抛出的异常,让调用者决定是否处理异常。示例代码:
public void doSomething() throws SomeException {
// 可能抛出异常的代码
}
4.4 finally块
finally块中的代码无论是否发生异常,都会执行。通常用于资源释放或清理操作。示例代码:
try {
// 可能抛出异常的代码
} catch (Exception e) {
// 异常处理逻辑
} finally {
// 清理代码
}
5. 最佳实践和规范
5.1 最佳实践
-
选择正确的异常类型
选择合适的异常类型能更准确地传达问题,提高代码可读性。不要滥用通用的异常类型,而是根据情况使用Java标准异常类或自定义异常类。 -
不滥用异常处理
异常处理应用于异常情况,不应该用于正常流程控制。避免在程序逻辑中过多地使用异常来代替条件判断。 -
日志记录与异常信息
在异常处理中,使用合适的日志记录工具(如Log4j、Slf4j)记录异常信息,有助于后续问题定位和分析。 -
自定义异常类
根据业务需要,可以自定义异常类,提供更详细的异常信息,增强代码的可读性。自定义异常类应继承于Exception或其子类。
5.2 规范
- 异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
- 严禁对大段代码进行try-catch。catch 时请区分稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能区分异常类型,再做对应的异常处理。
- 严禁捕获异常却什么都不处理就又抛出,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转换为用户可以理解的内容。
- 捕获(catch)异常,如果处理后还继续向上抛出,不用记录 ERROR 日志、不用打印堆栈信息;如果捕获异常后处理掉,必须在 catch 语句块中记录 ERROR 级别的日志。
- 不能再 finally 块中 使用 return,finally 块中的 return 返回后方法执行结束,不会再执行 try 块中的 return 语句。
- 应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J 中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
- 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。
6. 代码实例
6.1 捕获和处理异常
try {
int result = 10 / 0; // 除以零
} catch (ArithmeticException e) {
// 记录exception,到时候方便排查问题
log.error("发生算术异常,{}",e);
}
注意:尽量不要使用e.printStackTrace(),而是使用log打印,并记录exception,不要用一个Exception捕捉所有可能的异常
- printStackTrace()打印出的堆栈日志跟业务代码日志是交错混合在一起的,排查异常日志不太方便。
- e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,那么,用户的请求就卡住啦
6.2 抛出异常
public void withdraw(int amount) throws InsufficientFundsException {
if (balance < amount) {
throw new InsufficientFundsException("余额不足");
}
// 其他操作
}
6.3 使用finally块
try {
// 打开文件
} catch (IOException e) {
// 处理异常
} finally {
// 关闭文件
}
注意: 或者直接使用try-with-resource
使用JDK7的新特性try-with-resource来处理,它是Java7提供的一个新功能,它用于自动资源管理。
- 资源是指在程序用完了之后必须要关闭的对象。
- try-with-resources保证了每个声明了的资源在语句结束的时候会被关闭
- 只要实现了java.lang.AutoCloseable接口或者java.io.Closeable接口的对象,都OK。
try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) {
// use resources
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
6.4 自定义异常类的应用
public class MyCustomException extends Exception {
public MyCustomException(String message) {
super(message);
}
}
public void doSomething() throws MyCustomException {
if (/* 某些条件不满足 */) {
throw new MyCustomException("发生自定义异常");
}
// 其他操作
}
6.5 运行时异常 RuntimeException
运行时异常 RuntimeException,不应该通过 catch 的方式来处理,而是先预检查,比如:NullPointerException 处理
if (obj != null){
...
}
结论
异常处理是Java编程中不可或缺的一部分,通过合理地使用异常处理机制,可以提高程序的稳定性、可维护性和可读性。在代码中遵循异常处理的最佳实践和规范,能够让你的程序更加健壮且易于管理。通过学习本文提供的概念、用法和代码实例,你将能够更加深入地理解和应用Java异常处理机制。