Finally
程序块中完成资源释放或者使用Try-With
语句比如对于InputStream
,当我们使用完毕,我们要确保资源被正确关闭,比如下面我们常见的错误用法,不要在try
模块中关闭资源,因为一旦try
语句块中的其他方法发生异常,很有可能无法执行到inputStream.close()
方法的。
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);
}
}
推荐做法,使用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
语句来确保资源文件可以正常关闭
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);
}
}
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException {
...
}
比如,对于NumberFormatException
这类异常,异常本身已经告诉你错误类型,所以只需要传入的转换字符串,然而,对于一些无法帮助我们定位错误信息的异常,必要的描述性信息还是很有必要的。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: “xyz”
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
Throwable
注意,
Throwable
是所有异常(Exception)和错误(Error)的父类,如果在try-catch
语句中使用了Throwable
,你不仅捕获了所有的异常,还包括所有的错误error
,一旦JVM
抛出了Error
,就意味着程序发生了严重的错误,应用程序本身已无法自行处理,比如我们常见的OutOfMemoryError
和StackOverflowError
,此种类型错误的发生意味者已经超出应用程序本身所能处理的范畴,所以,不要在代码中试图捕获Throwable
,除非你十二分确认异常情况,并且有能力自行处理。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
某些编程人员可能会盲目笃信彼时彼地
某个代码段永远都不会发生异常,并且很自信的加上了一个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);
}
}
这可能是我们最容易忽视的一种代码实践,即:不要同时进行日志记录并抛出异常。
下面是我们最常见的一种异常代码书写方式,这也是我们异常处理的最常见方式,首先记录日志,然后重新抛出异常,但其实这并不是一种好的代码实践:
ry {
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,异常堆栈信息已经明确的告知了我们异常发生的类、方法以及错误行,所以,我们重复记录是没有意义的,如果我们需要对异常额外增加辅助说明,那么我们可以将异常封装为自定义异常,然后重新抛出
,就像下面这样,记住,这样做的前提是遵循下面我们所即将探讨的最佳实践#9
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
吃
掉任何异常信息有时候,我们常常需要将异常封装为自定义异常,但切记,封装时不要吃掉
任何标准的异常信息,切记保留异常最初发生的原因,我们可以通过Exception
类提供的特定的可以接受Throwable
类型的构造方法来实现自定义异常,否则,我们将丢失异常发生堆栈信息,进而导致我们分析问题困难。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java