引言

在Java编程中,异常处理是一个至关重要的环节,它不仅涉及到程序的稳定性和安全性,还关系到用户体验和系统资源的合理利用。合理的异常处理能够使得程序在面对不可预知错误时,能够优雅地恢复或者给出明确的反馈,而不是简单地崩溃退出。

文章开始前,我们先看下思维导图熟悉下有哪些异常

正文

1、尽量不要捕获 RuntimeException(Unchecked Exception)

阿里巴巴Java开发手册上这样规定:

尽量不要 catch RuntimeException,比如 NullPointerException、IndexOutOfBoundsException 等等,应该用预检查的方式来规避。

正例

if (obj != null) {
//...
}
" + (a / b));
}

反例

try {
obj.method();
} catch (NullPointerException e) {
//...
}

如果有些异常预检查不出来呢?比如说 NumberFormatException,虽然也属于 RuntimeException,但没办法预检查,所以还是应该用 catch 捕获处理。

2、 切勿在代码中使用异常来进行流程控制

异常应当是在真正的异常情况下使用,而不是用来控制程序流程。

public class Demo {
public static void main(String[] args) {
String input = "1,2,3,a,5";
String[] values = input.split(",");
for (String value : values) {
try {
int num = Integer.parseInt(value);
System.out.println(num);
} catch (NumberFormatException e) {
System.err.println(value + " is not a valid number");
}
}
}
} }

3、 合理利用finally块

确保在finally块中释放资源,比如关闭文件流或数据库连接,无论是否发生异常。

FileInputStream file = null;
try {
file = new FileInputStream("someFile.txt");
// 使用文件流
} catch (IOException e) {
e.printStackTrace();
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

4、 不要在finally块中使用return

这会导致try块中的return语句被忽略。

public int notGood() {
try {
// 假设这里有逻辑代码
return 1;
} finally {
return 2;
}
}

5、 避免忽略异常

即使认为某些异常不重要也不应该完全忽略它们,至少要记录下来。

反例

public void doNotIgnoreExceptions() {
try {
} catch (NumberFormatException e) {
// 没有记录异常
}
}

正例

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

6、 捕获具体的子类而不是捕获 Exception 类

对不同类型的异常给出不同的处理逻辑。

反例

try {
someMethod();
} catch (Exception e) { //错误方式
LOGGER.error("method has failed", e);
}

正例

try {
// 某些可能产生异常的操作
} catch (FileNotFoundException e) {
// 文件未找到的处理逻辑
} catch (IOException e) {
// IO异常的处理逻辑
}

7、 将所有相关信息尽可能地传递给异常

有用的异常消息和堆栈跟踪非常重要,如果你的日志不能定位异常位置,那要日志有什么用呢?

try {
// 某些可能产生异常的操作
} catch (IOException | SQLException e) {
// Log exception message and stack trace
LOGGER.debug("Error reading file", e);
}

应该尽量把 String message, Throwable cause 异常信息和堆栈都输出。

8、 使用自定义异常传递更多信息

当内置的异常类型不能满足需求时,可以创建自定义异常。

public class MyException extends Exception {
public MyException(String message) {
super(message);
}
} public void doSomething() throws MyException {
// 某些逻辑
throw new MyException("特定错误信息");
}

9、 自定义异常时不要丢失堆栈跟踪

在捕获一个异常并抛出另一个异常时,保留原始异常的信息。

catch (NoSuchMethodException e) {
//错误方式
throw new MyServiceException("Some information: " + e.getMessage());
}

这破坏了原始异常的堆栈跟踪,正确的做法是:

catch (NoSuchMethodException e) {
//正确方式
throw new MyServiceException("Some information: " , e);
}

10、优先使用标准异常

在可能的情况下,应优先使用Java标准库中定义的异常。

public void setValue(int value) {
if (value < 0) {
throw new IllegalArgumentException("值不能为负"); // 使用标准异常
}
// 设置值的逻辑
}

11、不要在生产环境中使用 printStackTrace()

在 Java 中,printStackTrace() 方法用于将异常的堆栈跟踪信息输出到标准错误流中。这个方法对于调试和排错非常有用。但在生产环境中,不应该使用 printStackTrace() 方法,因为它可能会导致以下问题:

  • printStackTrace() 方法将异常的堆栈跟踪信息输出到标准错误流中,这可能会暴露敏感信息,如文件路径、用户名、密码等。
  • printStackTrace() 方法会将堆栈跟踪信息输出到标准错误流中,这可能会影响程序的性能和稳定性。在高并发的生产环境中,大量的异常堆栈跟踪信息可能会导致系统崩溃或出现意外的行为。
  • 由于生产环境中往往是多线程、分布式的复杂系统,printStackTrace() 方法输出的堆栈跟踪信息可能并不完整或准确。

在生产环境中,应该使用日志系统来记录异常信息,例如** log4j、slf4j、logback**等。日志系统可以将异常信息记录到文件或数据库中,而不会暴露敏感信息,也不会影响程序的性能和稳定性。同时,日志系统也提供了更多的功能,如级别控制、滚动日志、邮件通知等。

//例如,可以使用 logback 记录异常信息,如下所示:
try {
// some code
} catch (Exception e) {
logger.error("An error occurred: ", e);
}

12、 不要捕获 Throwable

Throwable 是 exception 和 error 的父类,如果在 catch 子句中捕获了 Throwable,很可能把超出程序处理能力之外的错误也捕获了。

public void doNotCatchThrowable() {
try {
} catch (Throwable t) {
// 不要这样做
}
}

13、利用try-with-resources自动管理资源

Java 7引入的try-with-resources语句可以自动管理资源,减少代码冗余。

try (FileInputStream input = new FileInputStream("file.txt")) {
// 使用资源
} catch (IOException e) {
e.printStackTrace();
}

14、为异常提供详细的上下文信息

在抛出异常时,提供足够的上下文信息,以帮助定位和解决问题。

public void loadConfiguration(String path) throws IOException {
try {
// 加载配置逻辑
} catch (IOException e) {
throw new IOException("加载配置文件失败,路径:" + path, e);
}
}

15、不要记录了异常又抛出了异常

这纯属画蛇添足,并且容易造成错误信息的混乱。

反例:

try {
} catch (NumberFormatException e) {
log.error(e);
throw e;
}

要抛出就抛出,不要记录,记录了又抛出,等于多此一举。

正例:

public void wrapException(String input) throws MyBusinessException {
try {
} catch (NumberFormatException e) {
throw new MyBusinessException("错误信息描述:", e);
}
}

16、finally 块中不要抛出任何异常

try {
someMethod(); //Throws exceptionOne
} finally {
cleanUp(); //如果finally还抛出异常,那么exceptionOne将永远丢失
}

finally 块用于定义一段代码,无论 try 块中是否出现异常,都会被执行。finally 块通常用于释放资源、关闭文件等必须执行的操作。

如果在 finally 块中抛出异常,可能会导致原始异常被掩盖。比如说上例中,一旦 cleanup 抛出异常,someMethod 中的异常将会被覆盖

17、只抛出和方法相关的异常

相关性对于保持代码的整洁非常重要。一种尝试读取文件的方法,如果抛出 NullPointerException,那么它不会给用户提供有价值的信息。相反,如果这种异常被包裹在自定义异常中,则会更好。NoSuchFileFoundException 则对该方法的用户更有用。

public class Demo {
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println("The result is: " + result);
} catch (ArithmeticException e) {
System.err.println("Error: " + e.getMessage());
}
} public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}
}

18、尽早验证用户输入以在请求处理的早期捕获异常

再好的异常捕获不如没有异常,所以我们在业务开发中,可以提前验证用户输入,以在请求处理的早期就捕获处理异常

举个例子,我们用 JDBC 的方式往数据库插入数据,那么最好是先 validate 再 insert,而不是 validateUserInput、insertUserData、validateAddressInput、insertAddressData。

Connection conn = null;
try {
// Connect to the database
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password"); // Start a transaction
conn.setAutoCommit(false); // Validate user input
validateUserInput(); // Insert user data
insertUserData(conn); // Validate address input
validateAddressInput(); // Insert address data
insertAddressData(conn); // Commit the transaction if everything is successful
conn.commit(); } catch (SQLException e) {
// Rollback the transaction if there is an error
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
System.err.println("Error: " + ex.getMessage());
}
}
System.err.println("Error: " + e.getMessage());
} finally {
// Close the database connection
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.err.println("Error: " + e.getMessage());
}
}
}

19、 一个异常只能包含在一个日志中

反例

log.debug("Using redis one");
log.debug("Using redis two");

在单线程环境中,这样看起来没什么问题,但如果在多线程环境中,这两行紧挨着的代码中间可能会输出很多其他的内容,导致问题查起来会很难受。应该这样做:

LOGGER.debug("Using redis one, Using redis two");

20、对于不打算处理的异常,直接使用 try-finally,不用 catch

try {
method1(); // 会调用 Method 2
} finally {
cleanUp(); //do cleanup here
}

如果 method1 正在访问 Method 2,而 Method 2 抛出一些你不想在 Method 1 中处理的异常,但是仍然希望在发生异常时进行一些清理,可以直接在 finally 块中进行清理,不要使用 catch 块。

总结

有效的异常处理是高质量Java应用开发的基石。也是Java面试中被问频率很高的问题,通过遵循上述20个最佳实践,开发者不仅能够编写出更加健壮和可维护的代码,还能应对面试中面试官的各种关于异常的问题。

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

本文,已收录于,我的技术网站 aijiangsir.com,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激![]()

Java异常处理的20个最佳实践:告别系统崩溃的更多相关文章

  1. Java 异常处理的 20 个最佳实践,你知道几个?

    异常处理是 Java 开发中的一个重要部分,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java 提供了几个异常处理特性,以try,catch 和 finally 关键字的形式内建 ...

  2. 《转载》Java异常处理的10个最佳实践

    本文转载自 ImportNew - 挖坑的张师傅 异常处理在编写健壮的 Java 应用中扮演着非常重要的角色.异常处理并不是功能性需求,它需要优雅地处理任何错误情况,比如资源不可用.非法的输入.nul ...

  3. Java异常处理的10个最佳实践

    本文作者: ImportNew - 挖坑的张师傅 未经许可,禁止转载! 异常处理在编写健壮的 Java 应用中扮演着非常重要的角色.异常处理并不是功能性需求,它需要优雅地处理任何错误情况,比如资源不可 ...

  4. Java 异常处理的 9 个最佳实践

    在 Java 中,异常处理是个很麻烦的事情.初学者觉得它很难理解,甚至是经验丰富的开发者也要花费很长时间决定异常是要处理掉和抛出. 所以很多开发团队约定一些原则处理异常.如果你是一个团队的新成员,你可 ...

  5. Java异常处理的9个最佳实践

    无论你是新手还是资深程序员,复习下异常处理的实践总是一件好事,因为这能确保你与你的团队在遇到问题时能够处理得了它. 在 Java 中处理异常并不是一件易事.新手觉得处理异常难以理解,甚至是资深开发者也 ...

  6. 大规模使用 Apache Kafka 的20个最佳实践

    必读 | 大规模使用 Apache Kafka 的20个最佳实践 配图来源:书籍<深入理解Kafka> Apache Kafka是一款流行的分布式数据流平台,它已经广泛地被诸如New Re ...

  7. Kafka在大型应用中的 20 项最佳实践

    原标题:Kafka如何做到1秒处理1500万条消息? Apache Kafka 是一款流行的分布式数据流平台,它已经广泛地被诸如 New Relic(数据智能平台).Uber.Square(移动支付公 ...

  8. Java 编程中关于异常处理的 10 个最佳实践

    异常处理是Java 开发中的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch 和 ...

  9. Java 处理异常 9 个最佳实践,你知道几个?

    1. 在Finally中清理资源或者使用Try-With-Resource语句 使用Finally Java 7的Try-With-Resource语句 2. 给出准确的异常处理信息 3. 记录你所指 ...

  10. 说出几点 Java 中使用 Collections 的最佳实践?

    这是我在使用 Java 中 Collectionc 类的一些最佳实践: a)使用正确的集合类,例如,如果不需要同步列表,使用 ArrayList 而不是 Vector. b)优先使用并发集合,而不是对 ...

随机推荐

  1. [转帖]15--k8s之安全认证

    https://www.cnblogs.com/caodan01/p/15137987.html 目录 一.访问控制概述 二.认证管理 三.授权管理 虽然authorization-clusterro ...

  2. [转帖]061、监控指标之TiKV

    资源相关 Grafana监控 TiKV-Details -> Cluster - Store Size / Available Size Grafana监控 TiKV-Details -> ...

  3. [转帖]Region 性能调优

    https://docs.pingcap.com/zh/tidb/v6.5/tune-region-performance 本文介绍了如何通过调整 Region 大小等方法对 Region 进行性能调 ...

  4. vue3跟新视图遇见神奇现象

    场景描述 今天遇见一个问题, tableAllFun 函数中写了一个 index=1; 然后在 otherAllFun 函数中去改变这个index=2的值 奇怪的事情发生了 在视图index展示的值是 ...

  5. element-ui中Select 选择器去除点击时的淡蓝色边框

    <el-select class="my-el-select" v-model="tenantCont" placeholder="请输入机构标 ...

  6. widows 安装docker

    1.安装docker 依次安装如下两个文件: 如启动docker报错:可以是hv没有开启,按如下方法解决 (23条消息) Windows10启动Docker报错:Hardware assisted v ...

  7. HTML直接插入js、css

    简单的小页面可以使用 代码量大的话还是建议引用代码 直接包裹起来 <style>这里添加css代码</style> 加入css标识 <style type="t ...

  8. C++ CryptoPP使用RSA加解密

    Crypto++ (CryptoPP) 是一个用于密码学和加密的 C++ 库.它是一个开源项目,提供了大量的密码学算法和功能,包括对称加密.非对称加密.哈希函数.消息认证码 (MAC).数字签名等.C ...

  9. 19.6 Boost Asio 文本压缩传输

    Base64是一种二进制到文本的编码方案,用于将二进制数据转换为ASCII字符串格式.它通过将二进制数据流转换为一系列64个字符来工作,这些字符都可以安全地传输到设计用于处理文本数据的系统中. 如下代 ...

  10. 14.3 Socket 字符串分块传输

    首先为什么要实行分块传输字符串,一般而言Socket套接字最长发送的字节数为8192字节,如果发送的字节超出了此范围则后续部分会被自动截断,此时将字符串进行分块传输将显得格外重要,分块传输的关键在于封 ...