9种经常使用便捷的Java异常处理方法,帮你脱身繁琐

前言

Java中的异常处理是个不简单的话题。初学者很难理解,即便是经验丰富的开发人员也能够花费数小时来讨论如何以及应该抛出或处理哪些异常。java

这就是为何大多数开发团队都有一套关于如何使用它们的规则的缘由。并且,若是您是团队新手,那么您可能会感到惊讶,这些规则与您之前使用的规则有何不一样。面试

尽管如此,大多数团队仍是采用了几种最佳实践。如下是9个最重要的信息,它们能够帮助您入门或改善异常处理。app

1、在finally块中清理资源或使用Try-With-resource语句

常常发生的是,您在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块中的全部语句将被执行,而且资源将被关闭。ide

可是您添加try块是有缘由的。您调用一个或多个可能引起异常的方法,或者您可能本身引起异常。这意味着您可能未到达try块的末尾。所以,您将不会关闭资源。函数

所以,您应该将全部清理代码放入finally块中,或使用try-with-resource语句。工具

使用finally模块this

与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);
            }
        }
    }
}复制代码

Java 7的Try-With-Resource语句另外一个选择是try-with-resource语句,我在Java异常处理简介中对其进行了详细说明。code

若是您的资源实现了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);
    }
}复制代码

2、指定具体的异常

抛出的异常越具体越好。始终牢记,不知道您的代码,或者可能几个月后不知道您的代码的同事,须要调用您的方法并处理该异常。

所以,请确保为他们提供尽量多的信息。这使您的API更易于理解。结果,您的方法的调用者将可以更好地处理该异常,或者经过额外的check避免该异常。

所以,请始终尝试查找最适合您的异常事件的类,例如,抛出NumberFormatException而不是.

IllegalArgumentException。并避免引起不肯定的Exception。

public void doNotDoThis() throws Exception {
    ...
}
public void doThis() throws NumberFormatException {
    ...
}复制代码

整理了一下2021年的Java工程师经典面试真题,共485页大概850道含答案的面试题PDF,包含了Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等几乎全部技术栈,每一个技术栈都有很多于50道经典面试真题,不敢说刷完包你进大厂,但有针对性的刷让你面对面试官的时候多几分底气仍是没问题的。

3、对特定异常进行归档

每当在方法签名中指定异常时,也应在Javadoc中对其进行记录。这与之前的最佳实践具备相同的目标:为呼叫者提供尽量多的信息,以便他能够避免或处理异常。

所以,请确保在Javadoc中添加一个@throws声明,并描述可能致使异常的状况。

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

4、抛出异常的时候包含描述信息

最佳实践背后的想法与前两个相似。可是这一次,您没有将信息提供给您的方法的调用者。每一个必须了解该日志文件或您的监视工具中报告该异常时发生的状况的人均可以阅读该异常的消息。

所以,它应尽量准确地描述问题,并提供最相关的信息以了解异常事件。

不要误会个人意思;您不该该写一段文字。可是您应该用1-2个简短的句子来讲明出现异常的缘由。这能够帮助您的运营团队了解问题的严重性,还可使您更轻松地分析任何服务事件。

若是抛出特定的异常,则其类名极可能已经描述了错误的种类。所以,您无需提供不少其余信息。一个很好的例子是NumberFormatException。当您以错误的格式提供String时,它将由类java.lang.Long的构造函数引起。

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均可以帮助您得到最佳实践。当您尝试首先捕获不太具体的异常时,它们报告没法访问的代码块。

问题在于仅执行与异常匹配的第一个catch块。所以,若是您首先捕获IllegalArgumentException,那么您将永远不会到达应该处理更具体的NumberFormatException的catch块,由于它是IllegalArgumentException的子类。

始终首先捕获最具体的异常类,并将不那么具体的捕获块添加到列表的末尾。

您能够在如下代码片断中看到这样的try-catch语句的示例。第一个catch块处理全部NumberFormatException,第二个全部IllegalArgumentException,它们不是NumberFormatException

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

6、不要捕获Throwable异常

Throwable是全部异常和错误的超类。您能够在catch子句中使用它,但绝对不要这样作!

若是在catch子句中使用Throwable,它将不只捕获全部异常,并且还捕获全部Exception。它还会捕获全部Error。JVM抛出严重的错误问题,这些问题不会由应用程序处理。

好比说:OutOfMemoryError或StackOverflowError。

二者都是由应用程序没法控制的状况引发的,没法处理。

所以,最好不要捕获Throwable,除非您彻底肯定本身处于特殊状况下,在这种状况下您可以或被要求处理错误。

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

7、不要忽略异常

您是否曾经分析过仅在用例的第一部分获得执行的错误报告?

这一般是由忽略的异常引发的。开发人员可能很是肯定不会将其抛出,并添加了一个不会处理或记录它的catch块。而且,当您找到该块时,您极可能甚至找到了著名的“这将永远不会发生”注释之一。

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)
在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);
    }
}复制代码

所以,仅在要处理它时才捕获异常。不然,请在方法签名中指定它,而后让调用者来处理它。

9、在不消耗异常的状况下包装异常

有时最好捕获一个标准异常并将其包装到自定义异常中。这种例外的典型示例是特定于应用程序或框架的业务例外。这使您能够添加其余信息,还能够对异常类实施特殊处理。

执行此操做时,请确保将原始异常设置为缘由。该异常类提供了接受一个特定的构造方法的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的可用性。

异常一般是同时存在的错误处理机制和通讯介质。所以,您应该确保与同事讨论要应用的最佳实践和规则,以便每一个人都能理解通常概念并以相同的方式使用它们。

相关文章
相关标签/搜索