P7阿里大牛实例讲解Java异常处理

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

在实际开发中会常常遇到在 try 中使用资源的状况,好比一个 InputStream ,在使用后你须要关闭它。在这种状况下,一个常见的错误是在 try 的尾部关闭了资源。java

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 中的代码都将被正常执行,资源也会被关闭。app

可是,用 try 老是有缘由的。当你调用一个或多个可能会抛出异常的方法或本身主动抛出异常时,程序可能会没法到达 try 的尾部。因而在最后,资源将不被关闭。框架

由于,你应该将全部清理资源的代码放进 finally 中,或使用 try-with-resource 语句。函数

使用 Finally

与 try 相比,不管是 try 中的代码被成功执行,仍是在 catch 中处理了一个异常后,Finally 中的代码总会被执行。所以,你能够确保全部已打开的资源都将被关闭。学习

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 语句,在个人这篇 Java 异常处理入门 中有更为详细的介绍。this

若是你在资源中实现了 AutoCloseable 接口的话,就可使用 try-with-resource 语句了,这也是大多数 Java 标准资源的作法。若是你在 try-with-resource 中打开了一个资源,在 try 中的代码被执行或异常处理后,这个资源将会被自动关闭。spa

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);
 }
}

2、抛出更具体的异常

你抛出的异常越具体、越明确越好。时刻牢记这点,特别是若是有一位并不了解你代码的同事,或几个月后的你须要调用本身的方法并处理异常时。.net

所以,你须要确保提供尽量多的信息,这会使得你的 API 更易于理解。这样,调用你方法的人能够更好地处理异常,从而避免额外的诸如 此类的检查 。日志

因此,应该找到与你的异常事件最符合的类,好比抛出一个 NumberFormatException 而不是 IllegalArgumentException (注:例如将参数转换为数值出错时,应该抛出具体的 NumberFormatException ,而不是笼统的 IllegalArgumentException )。请避免抛出一个不具体的异常。orm

public void doNotDoThis() throws Exception {
 ...
}public void doThis() throws NumberFormatException {
 ...
}

3、为你的异常编写文档

当你在方法签名中 指定一个异常 时,你也应该在 Javadoc 中记录它 。

因此,请确保在 Javadoc 中增长 @throws 声明,并描述可能会致使异常的状况。

/**
 * This method does something extremely useful ...
 *
 * @param input
 * @throws MyBusinessException if ... happens
 */public void doSomething(String input) throws MyBusinessException {
 ...
}

4、将描述信息与异常一同抛出

这个方法背后的思想和前两个是相似的。但这一次,你没必要给你的方法调用者提供信息。对于任何遭遇异常错误并须要搞清楚错误缘由的人来讲,异常信息老是在异常出现的同时,被记录在了日志中,或打印在了屏幕上。

所以,请尽量精确地描因此,最好不要在 catch 中使用 Throwable ,除非你能确保本身处于一些特定状况下,好比你本身足以处理错误,又或被要求处理错误时。述异常事件,并提供最相关的信息以令其余人可以理解发生了什么异常时。

别误会个人意思了。你不必去写上一大段的文字,但你应该用一两句简短的话来解释一下异常发生的缘由。这能让你的开发团队明白问题的严重性,也能让你更容易地分析服务事故。

若是你抛出了一个特定的异常,它的类名极可能就已经描述了这是什么类型的错误了。因此,你不须要提供不少额外的描述信息。一个很好的例子是,当你提供了一个错误格式的 String 类型参数时,java.lang.Long 构造函数就会抛出 NumberFormatException 。

try {
 new Long("xyz");
} catch (NumberFormatException e) {
 log.error(e);
}

NumberFormatException 的类名已经告诉了你问题的类型。因此异常信息只须要返回致使问题的输入字符串就好了。若是异常类的名字不能代表其含义,那么你还须要在异常信息中提供必要的解释信息。

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

5、优先捕获具体的异常

大多数 IDE 都能帮你作到这点。当你尝试优先捕获不那么具体的异常时, IDE 会报告给你这是一个不能到达的代码块。

这个问题的缘由是只有第一个匹配到异常的 catch 块才会被执行。因此,若是你先 catch 了一个 IllegalArgumentException ,你将永远没法到达处理更具体异常 NumberFormatException 的 catch 块中,由于 NumberFormatException 是 IllegalArgumentException 的子类。

因此,请优先捕获更具体的异常,并把不那么具体的 catch 块放在后面。

在下面你能够看到这样的一个 try-catch 语句示例。第一个 catch 处理全部的 NumberFormatExceptions 异常,第二个 catch 处理 NumberFormatException 异常之外的 illegalargumentexception 异常。

public void catchMostSpecificExceptionFirst() { try {
 doSomething("A message");
 } catch (NumberFormatException e) { log.error(e);
 } catch (IllegalArgumentException e) { log.error(e)
 }
}

6、不要捕获 Throwable

Throwable 是全部 exceptions 和 errors 的父类。虽然你能够在 catch 子句中使用它,但你应该永远别这样作!

若是你在 catch 子句中使用了 Throwable ,它将不只捕获全部异常,还会捕获全部错误。这些错误是由 JVM 抛出的,用来代表不打算由应用处理的严重错误。 OutOfMemoryError 和 StackOverflowError 就是典型的例子,这两种状况都是由一些超出应用控制范围的状况致使的,没法处理。

因此,最好不要在 catch 中使用 Throwable ,除非你能确保本身处于一些特定状况下,好比你本身足以处理错误,又或被要求处理错误。

public void doNotCatchThrowable() { try { // do something
 } catch (Throwable t) { // don't do this!
 }
}

7、不要忽略异常

你分析过只有用例的第一部分代码被执行的 bug 报告吗?

这一般是因为忽略异常而致使的。开发者可能十分肯定这个异常不会被抛出,而后添加了一个没法处理或没法记录这个异常的 catch 。当你找到这个 catch 时,你极可能会发现这么一句著名的注释: “This will never happen”。

public void doNotIgnoreExceptions() { try { // do something
 } catch (NumberFormatException e) { // this will never happen
 }
}

没错,你可能就是在分析一个永远也不会发生的问题。

因此,请你务必不要忽略异常。你不知道代码在未来会经历怎样的改动。有些人可能会误删异常事件的验证,而彻底没意识到这会产出问题。或者抛出异常的代码被修改了,相同的类被抛出了多个异常,而调用它们的代码并不能阻止这些异常发生。

你至少应该把日志信息打印出来,告诉那些无心识下错误操做的人须要检查这里。

public void logAnException() { try { // do something
 } catch (NumberFormatException e) { log.error("This should never happen: " + e);
 }
}

8、不要同时打印并抛出异常

这多是本文中最常被忽略的一条实践准则了。你能够在许多代码片断甚至库中发现这个问题,异常被捕获,打印,再被从新抛出。

try { new Long("xyz");
} catch (NumberFormatException e) { log.error(e); throw e;
}

这样也许会很直观地看到被打印的异常,异常再被从新抛出,调用者也能很好地处理它。但这样会使多个错误信息被同个异常给打印出来。

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条准则中所述,异常信息应该准确描述异常事件。 Stack Trace (堆栈追踪)会告诉你异常在哪一个类、哪一个方法、哪一个行中被抛出。

若是你须要添加额外的信息,你应该将异常捕获并包装在自定义的的异常中,但要确保遵循下面的第9条实践准则。

public void wrapException(String input) throws MyBusinessException { try { // do something
 } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e);
 }
}

因此,只有在你想要处理一个异常的时候才去捕获它。不然,在方法签名处指明这个异常让调用者关注就行了。

9、包装异常但不要丢弃原始异常

有时候将异常包装成一个自定义异常会比捕捉一个标准异常要更好。一个典型的例子是应用或框架的特定业务异常。这容许你添加额外的信息,也能为你的异常类实现一个特定的处理方法。

当你这么作的时候,必定要确保原始的异常设为 cause 。 Exception 类提供了一系列的特定构造方法,这些方法能够接受 Throwable 做为参数(注:如 Exception(String message, Throwable cause) )。不然,你将会丢失原始异常的 stack trace 与信息,这会使你分析致使异常的事件变得十分困难。

public void wrapException(String input) throws MyBusinessException { try { // do something
 } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e);
 }
}

总结

如你所见,当决定该抛出仍是捕获异常时候,你须要去考虑不少方面。以上的大多数实践准则都是为了提升你代码和 API 的可读性与可用性。

异常是不只是一个错误处理机制,同时也是一个沟通媒介。所以,你应该与你的同事一块儿讨论哪些是你想要应用的最佳实践与准则,以便全部人都能理解相关的基本概念,并用一样的方式在实际中应用这些准则。

分享一些知识点给你们但愿能帮助到你们,或者从中启发。

加Q君羊:821169538 都是java爱好者,你们一块儿讨论交流学习

相关文章
相关标签/搜索