Java中的异常处理不是一个简单的主题。初学者发现它很难理解,甚至有经验的开发者也能够花几个小时讨论如何以及应该抛出或处理哪些异常。html
这就是为何大多数开发团队都有本身的一套如何使用它们的规则。若是你是一个团队的新手,你可能会惊讶这些规则与你以前使用的规则有多么不一样。java
尽管如此,大多数团队都使用了几种最佳实践。如下是帮助你入门或改进异常处理的9个最重要的内容。api
在try块中使用资源是很频繁的,好比 InputStream ,以后须要关闭它。这些状况中的一个常见错误是在try块结束时关闭资源。架构
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块中的全部语句都将被执行,资源将被关闭。oracle
可是你添加了try块是有缘由的。你调用一个或多个可能抛出异常的方法,或者你本身抛出异常。这意味着你可能没法到达try块的末尾。所以,你将不会关闭资源。app
所以,你应该将全部清理代码放入finally块或使用try-with-resource语句。框架
与try块的最后几行相比,finally块始终执行。这能够在成功执行try块以后或在catch块中处理异常以后发生。所以,你能够确保清理全部已打开的资源。分布式
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); } } } }
另外一种选择是try-with-resource语句,我在对此进行了更详细的解释。函数
若是资源实现 AutoCloseable 接口,则可使用它。这就是大多数Java标准资源所作的事情。当你在try子句中打开资源时,它将在try块执行后自动关闭,或者处理异常。工具
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); } }
抛出的异常越具体越好。请记住,不明白你代码的同事,或者你可能在几个月后须要调用你的方法并处理异常。
所以,请务必提供尽量多的信息。这使你的API更易于理解。所以,你的方法的调用者将可以更好地处理异常或 经过额外的检查来避免它 。
所以,老是尝试找到最适合你的异常事件的类,例如抛出 NumberFormatException 而不是 IllegalArgumentException 。并避免抛出非特定的异常。
public void doNotDoThis() throws Exception { ... } public void doThis() throws NumberFormatException { ... }
不管什么时候在方法签名中,都应该 在Javadoc中记录它 。这与之前的最佳实践具备相同的目标:为调用者提供尽量多的信息,以便他能够避免或处理异常。
所以,请确保向Javadoc 添加@throws声明并描述可能致使异常的状况。
/** * This method does something extremely useful ... * * @param input * @throws MyBusinessException if ... happens */ public void doSomething(String input) throws MyBusinessException { ... }
这种最佳实践背后的想法相似于前两种实践。可是此次,你不向调用方提供有关方法的信息。每一个必须了解在日志文件或监视工具中抛出异常时发生了什么的人都会读取异常的消息。
所以,它应该尽量准确地描述问题,并提供最相关的信息来理解异常事件。
别误会个人意思; 你不该该写一段文字。可是你应该用1-2个简短的句子来解释这个例外的缘由。这有助于你的运营团队了解问题的严重性,还可让你更轻松地分析任何服务事件。
若是抛出一个特定的异常,它的类名极可能已经描述了那种错误。所以,你无需提供大量其余信息。一个很好的例子是NumberFormatException。它会被类java.lang.Long的构造函数抛出,当你以错误的格式提供String参数。
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"
大多数IDE均可以帮助你实现这一最佳实践。当你尝试首先捕获不太具体的异常时,它们提示没法访问的代码块。
问题是只有匹配异常的第一个catch块才会被执行。所以,若是首先捕获IllegalArgumentException,则永远不会到达应该处理更具体的NumberFormatException的catch块,由于它是IllegalArgumentException的子类。
始终优先捕获最具体的异常类,并将不太具体的catch块添加到列表的末尾。
你能够在如下代码段中看到此类try-catch语句的示例。第一个catch块处理全部的NumberFormatException,第二个处理全部不是NumberFormatException的IllegalArgumentException 异常。
public void catchMostSpecificExceptionFirst() { try { doSomething("A message"); } catch (NumberFormatException e) { log.error(e); } catch (IllegalArgumentException e) { log.error(e) } }
Throwable 是全部异常和错误的超类。你能够在catch子句中使用它,但你永远不该该这样作!
若是在catch子句中使用Throwable,它不只会捕获全部异常; 它还会捕获全部错误。JVM抛出错误以指示应用程序没法处理的严重问题。典型的例子是 OutOfMemoryError 或 StackOverflowError 。二者都是由应用程序没法控制的状况引发的,没法处理。
因此,最好不要抓住Throwable,除非你彻底肯定你处于一个特殊状况,你能够或者须要处理错误。
public void doNotCatchThrowable() { try { // do something } catch (Throwable t) { // don't do this! } }
你是否曾经分析过只有用例第一部分被执行的错误报告?
这一般是由忽略的异常引发的。开发人员可能很是肯定它永远不会被抛出并添加了一个不处理或记录它的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); } }
这多是此列表中最常被忽略的最佳作法。你能够找到许多代码片断,甚至是catch,log和从新throw异常的库。
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中所述,异常消息应描述异常事件。堆栈跟踪告诉你抛出异常的类,方法和行。
若是须要添加其余信息,则应捕获异常并将其包装在自定义异常中。但请务必遵循最佳作法9。
public void wrapException(String input) throws MyBusinessException { try { // do something } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e); } }
所以,若是你想要处理它,只捕获异常。不然,在方法签名中指定它并让调用者处理它。
有时候捕获标准异常并将其包装成自定义异常会更好。此类异常的典型示例是应用程序或框架特定的业务异常。这容许你添加其余信息,还能够为异常类实现特殊处理。
执行此操做时,请确保将原始异常设置为cause。该异常类提供了接受一个特定的构造方法的Throwable做为参数。不然,你将丢失原始异常的堆栈跟踪和消息,这将致使难以分析致使异常的异常事件。
public void wrapException(String input) throws MyBusinessException { try { // do something } catch (NumberFormatException e) { throw new MyBusinessException("A message that describes the error.", e); } }
正如所看到的,当你抛出或捕获异常时,你应该考虑许多不一样的事情。其中大多数都旨在提升代码的可读性或API的可用性。
异常一般同时是错误处理机制和通讯媒介。所以,您应该确保与同事讨论要应用的最佳实践和规则,以便每一个人都能理解通用概念并以相同的方式使用它们。
欢迎学Java和大数据的朋友们加入java架构交流: 855835163
加群连接:https://jq.qq.com/?_wv=1027&k=5dPqXGI 群内提供免费的架构资料还有:Java工程化、高性能及分布式、高性能、深刻浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的免费直播讲解 能够进来一块儿学习交流哦