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

在本文中,作者介绍了9个处理异常的最佳方法与实践,以举例与代码展示结合的方式,让开发者更好的理解这9种方式,并指导读者在不同情况下选择不同的异常处理方式。

以下为译文:

Java中的异常处理不是一个简单的话题。初学者很难理解,甚至有经验的开发人员也会花几个小时来讨论应该如何抛出或处理这些异常。

这就是为什么大多数开发团队都有自己的异常处理的规则和方法。如果你是一个团队的新手,你可能会惊讶于这些方法与你之前使用过的那些方法有多么不同。

然而,有几种异常处理的最佳方法被大多数开发团队所使用。下面是帮助改进异常处理的9个最重要的方法。

  1. 在Finally中清理资源或者使用Try-With-Resource语句

通常情况下,你在try中使用了一个资源,比如InputStream,之后需要关闭它。在这种情况下,一个常见的错误是在try的末尾关闭了资源。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public void doNotCloseResourceInTry() {

FileInputStream inputStream = null;

try {

File file = new File("./tmp.txt");

inputStream = new FileInputStream(file);

// use the inputStream to read a file

// do NOT do this

inputStream.close();

} catch (FileNotFoundException e) {

log.error(e);

} catch (IOException e) {

log.error(e);

}

}

问题是,只要不抛出异常,这种方法就可以很好地运行。try内的所有语句都将被执行,资源也会被关闭。

但是你在try里调用了一个或多个可能抛出异常的方法,或者自己抛出异常。这意味着可能无法到达try的末尾。因此,将不会关闭这些资源。

所以应该将清理资源的代码放入Finally中,或者使用Try-With-Resource语句。

使用Finally

相比于try,无论是在成功执行try里的代码后,或是在catch中处理了一个异常后,Finally里的内容是一定会被执行的。因此,可以确保清理所有已打开的资源。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public void closeResourceInFinally() {

FileInputStream inputStream = null;

try {

File file = new File("./tmp.txt");

inputStream = new FileInputStream(file);

// use the inputStream to read a file

} catch (FileNotFoundException e) {

log.error(e);

} finally {

if (inputStream != null) {

try {

inputStream.close();

} catch (IOException e) {

log.error(e);

}

}

}

}

Java 7的Try-With-Resource语句

另一个选择是Try-With-Resource语句,在introduction to Java exception handling中更详细地说明了这一点。

如果你的资源实现了AutoCloseable接口,就可以使用它,这正是大多数Java标准资源所做的。当你在try子句中打开资源时,它将在try被执行后自动关闭,或者处理一个异常。

1

2

3

4

5

6

7

8

9

10

public void automaticallyCloseResource() {

File file = new File("./tmp.txt");

try (FileInputStream inputStream = new FileInputStream(file);) {

// use the inputStream to read a file

} catch (FileNotFoundException e) {

log.error(e);

} catch (IOException e) {

log.error(e);

}

}

  1. 给出准确的异常处理信息

你抛出的异常越具体越好。一定要记住,一个不太了解你代码的同事,也许几个月后,需要调用你的方法,并且处理这个异常。

因此,请确保提供尽可能多的信息,这会使你的API更容易理解。因此,你方法的调用者将能够更好地处理异常,或者通过额外的检查来避免它。

所以,要尽量能更好地描述你的异常处理信息,比如用NumberFormatException代替IllegalArgumentException,避免抛出一个不具体的异常。

1

2

3

4

5

6

public void doNotDoThis() throws Exception {

...

}

public void doThis() throws NumberFormatException {

...

}

  1. 记录你所指定的异常

当你在方法中指定一个异常时,你应该在Javadoc中记录下它。这与前面提到的方法有着相同的目标:为调用者提供尽可能多的信息,这样他们就可以避免异常或者更容易地处理异常。

因此,请确保在Javadoc中添加一个@throws 声明,并描述可能导致的异常情况。

1

2

3

4

5

6

7

8

9

/**

  • This method does something extremely useful ...
  • @param input
  • @throws MyBusinessException if ... happens

*/

public void doSomething(String input) throws MyBusinessException {

...

}

  1. 使用描述性消息抛出异常

这一最佳实践的理念与前两个相似。但这一次,你不用给调用方法的人提供信息。异常消息会被所有人读取,同时必须了解在日志文件或监视工具中报告异常时发生了什么。

因此,应该尽可能准确地描述问题,并提供相关的信息来了解异常事件。

别误会,你不需要写一段文字,而是应该用1-2个简短的句子解释异常的原因。这可以帮助开发团队理解问题的严重性,同时也使你能够更容易地分析任何服务事件。

如果抛出一个特定的异常,它的类名很可能已经描述了这种类型的错误。所以,你不需要提供很多额外的信息。一个很好的例子就是,当你以错误的格式使用字符串时,如NumberFormatException,它就会被类 java.lang.Long的构造函数抛出。

1

2

3

4

5

try {

new Long("xyz");

} catch (NumberFormatException e) {

log.error(e);

}

NumberFormatException已经告诉你问题的类型,所以只需要提供导致问题的输入字符串。如果异常类的名称不具有表达性,那么就需要提供必要的解释信息。

1

17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"

  1. 最先捕获特定的异常

大多数IDE都可以帮助你做到这点,当你试图捕获不确定的异常时,它会报告一个不可到达的代码块。

问题是只有第一个匹配到异常的catch语句才会被执行,所以,如果你最先发现IllegalArgumentException,你将永远不会到达catch里处理更具体的NumberFormatException,因为它是IllegalArgumentException的一个子类。

所以要首先捕获特定的异常类,并在末尾添加一些处理不是很具体异常的catch语句。

你可以在下面的代码片段中看到这样一个try-catch语句的示例。第一个catch处理所有NumberFormatExceptions异常,第二个catch 处理NumberFormatException异常以外的illegalargumentexception异常。

1

2

3

4

5

6

7

8

9

public void catchMostSpecificExceptionFirst() {

try {

doSomething("A message");

} catch (NumberFormatException e) {

log.error(e);

} catch (IllegalArgumentException e) {

log.error(e)

}

}

  1. 不要在catch中使用Throwable

Throwable是exceptions 和 errors的父类。当然,你可以在catch子句中使用它,但其实你不应该这样做。

如果你在catch子句中使用Throwable,它将不仅捕获所有的异常,还会捕获所有错误。JVM会抛出错误,这是应用程序不打算处理的严重问题。典型的例子是OutOfMemoryError或StackOverflowError。这两种情况都是由应用程序控制之外的情况引起的,无法处理。

所以,最好不要在catch中使用Throwable,除非你完全确定自己处于一个特殊的情况下,并且你需要处理一个错误。

1

2

3

4

5

6

7

public void doNotCatchThrowable() {

try {

// do something

} catch (Throwable t) {

// don‘t do this!

}

}

  1. 不要忽略Exceptions

你是否曾经分析过只有用例的第一部分才被执行的bug报告吗?

这通常是由一个被忽略的异常引起的。开发人员可能非常确信它不会被抛出,并添加一个无法处理或无法记录它的catch语句。当你发现它的时候,你很可能就会明白一句著名的话“This will never happen”。

1

2

3

4

5

6

7

public void doNotIgnoreExceptions() {

try {

// do something

} catch (NumberFormatException e) {

// this will never happen

}

}

是的,你可能在分析一个不可能发生的问题。

所以,请千万不要忽略一个例外。你不会知道代码在将来会发生什么变化。有些人可能会删除阻止异常事件的验证,而没有意识到这造成了问题。或者抛出异常的代码被更改,现在抛出了同一个类的多个异常,而调用的代码并不能阻止所有这些异常。

你至少应该写一个日志信息,告诉每个人,需要检查一下这个问题。

1

2

3

4

5

6

7

public void logAnException() {

try {

// do something

} catch (NumberFormatException e) {

log.error("This should never happen: " + e);

}

}

  1. 不要记录和抛出一个异常

这可能是最常被忽略的。你可以在许多代码片段或者库文件里发现,有异常会被捕获、记录和重新抛出。

1

2

3

4

5

6

try {

new Long("xyz");

} catch (NumberFormatException e) {

log.error(e);

throw e;

}

当它发生时记录一个异常,然后重新抛出它,以便调用者能够适当地处理它,这可能会很直观。但是它会为同一个异常写多个错误消息。

1

2

3

4

5

6

7

17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"

Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"

at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

at java.lang.Long.parseLong(Long.java:589)

at java.lang.Long.(Long.java:965)

at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)

at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

不添加任何额外的信息。正如在上述第4个中所解释的那样,异常消息应该描述异常事件。堆栈会告诉你在哪个类、方法和行中异常被抛出。

如果你需要添加额外的信息,应该捕获异常并将其包装在一个自定义的信息中。但要确保遵循下面的第9条。

1

2

3

4

5

6

7

public void wrapException(String input) throws MyBusinessException {

try {

// do something

} catch (NumberFormatException e) {

throw new MyBusinessException("A message that describes the error.", e);

}

}

因此,只需要捕获一个你想要处理的异常,在方法中指定它,并让调用者处理它。

  1. 包装异常

有时最好捕获一个标准异常并将其封装到一个定制的异常中。此类异常的典型例子是应用程序或框架特定的业务异常。这允许你添加额外的信息,并且也可以为异常类实现一个特殊的处理。

当你这样做时,确保引用原始的异常处理。Exception类提供了一些特定的构造函数方法,这些方法可以接受Throwable作为参数。否则,你将丢失原始异常的堆栈跟踪和消息,这将使你很难分析导致异常的事件。

1

2

3

4

5

6

7

public void wrapException(String input) throws MyBusinessException {

try {

// do something

} catch (NumberFormatException e) {

throw new MyBusinessException("A message that describes the error.", e);

}

}

总结

正如你所看到的,在抛出或捕获异常时,有许多不同的事情需要考虑。以上大多数方法都可以提高代码可读性或API可用性。

异常通常是一个错误处理机制和一个通信媒介。因此,你应该确保同事一起讨论想要应用的最佳实践和方法,以便每个人都理解通用概念并以相同的方式使用它们。

原文地址:http://blog.51cto.com/13963248/2176158

时间: 2024-11-01 12:25:09

Java 中处理异常的 9 个最佳实践的相关文章

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

Java中处理异常的9个最佳实践Java中的异常处理不是一个简单的话题.初学者很难理解,甚至有经验的开发人员也会花几个小时来讨论应该如何抛出或处理这些异常. 这就是为什么大多数开发团队都有自己的异常处理的规则和方法.如果你是一个团队的新手,你可能会惊讶于这些方法与你之前使用过的那些方法有多么不同. 然而,有几种异常处理的最佳方法被大多数开发团队所使用.下面是帮助改进异常处理的9个最重要的方法. 在Finally中清理资源或者使用Try-With-Resource语句通常情况下,你在try中使用了

Atitit. 异常的使用总结最佳实践java .net php Vo8f

Atitit.java 异常的使用总结最佳实践 Vo8f 1. 为什么使用异常 1 2. 用throw抛出一个异常到catch子句中与通过函数调用传递一个参数两者基本相同. 2 3. S E H的主要动机 2 4. RuntimeException跟checked Exception 2 5. 要不要使用checked Exception 3 6. Exception业务流程控制 可以借鉴一下) 3 7. checked Exception 的缺点 4 8. Base类and 扩展class 抛

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

异常处理是书写 强健 Java应用的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了优雅的处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch和finally 关键字的形式内建于语言自身之中.Java编程语言也允许你创建新的异常,并通过使用  throw 和 throws关键字抛出它们.事实上,异常处理不仅仅是知道语法.书写一个强健的代码更多的是一门艺术而不仅仅是一门科学,这里我们将讨论一些关于异常处理的Java最佳实践.这些 J

夯实Java基础系列10:深入理解Java中的异常体系

目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调用链 自定义异常 异常的注意事项 当finally遇上return JAVA异常常见面试题 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 - Java异常 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.c

Java中的异常和处理详解

原文出处:代码钢琴家 简介 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常.异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?. Java提供了更加优秀的解决办法:异常处理机制. 异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰. Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手

JAVA中的异常(异常处理流程、异常处理的缺陷)

异常处理流程 1)首先由try{...}catch(Exception e){ System.out.println(e); e.printStackTrace(); }finally{...}结构 2)当JVM遇到异常时,会产生一个Exception对象 或 继承自Exception的子类的对象. 3)将异常对象向上层(调用它的代码块)抛出,知道碰到一个catch块(作相应处理) 或 一直抛到了最外层(导致程序异常终止).(并停止异常之后的代码的执行,但是finally块中的代码还会执行!换句

java中关于异常的处理

初学java的时候,当我们碰到异常时,一般会把异常直接throws抛出去,或则在catch的时候,简单的写一句打印异常信息,但是在实际开发中,是不能这么做的.如果我们将底层的某一个异常简单的print出去,那么这个异常的捕获是没有起到任何作用,反而给编译器一个"异常已处理"的假象,导致有异常的程序会继续往下执行,这是不允许的:如果我们直接将异常抛出去,抛给上一层,也是徒劳的,反而给上一层带来麻烦. 为了避免这样的情况发生,我们一般会将异常进行转换,将编译时异常转变成运行时异常,再thr

完成这个例子,说出java中针对异常的处理机制。

有一个类为ClassA,有一个类为ClassB,在ClassB中有一个方法b,此方法抛出异常,在ClassA类中有一个方法a,请在这个方法中调用b,然后抛出异常.在客户端有一个类为TestC,有一个方法为c ,请在这个方法中捕捉异常的信息.完成这个例子,请说出java中针对异常的处理机制. [java] view plaincopy package com.itheima; import java.io.IOException; /** *第6题:有一个类为ClassA,有一个类为ClassB,

Java中的异常-Throwable-Error-Exception-RuntimeExcetpion-throw-throws-try catch

今天在做一个将String转换为Integer的功能时,发现Integer.parseInte()会抛出异常NumberFormatException. 函数Integer.parseInt(String)定义 1 public static int parseInt(String s) 2 throws NumberFormatException 测试代码: 1 public class Test 2 { 3 public static void main(String[] args) 4 {