前言Java中的异常处理并不是一个轻松的话题。初学者很难理解,即使是经验丰富的开发人员也会花费数小时来讨论应该如何抛出或处理哪些异常以及应该抛出或处理哪些异常。这就是为什么大多数开发团队都有一套关于如何使用它们的规则。而且,如果您是团队的新成员,您可能会惊讶于规则与您习惯的规则有多么不同。尽管如此,大多数团队还是采用了一些最佳实践。以下是9条最重要的信息,可帮助您入门或改进异常处理。1.在finally块中清理资源或使用Try-With-resource语句。经常发生的情况是您在try块中使用资源(例如InputStream),然后需要关闭它。这些情况下的一个常见错误是在try块的末尾关闭资源。publicvoiddoNotCloseResourceInTry(){FileInputStreaminputStream=null;尝试{Filefile=newFile("./tmp.txt");inputStream=newFileInputStream(文件);//使用inputStream读取文件//不要这样做inputStream.close();}catch(FileNotFoundExceptione){log.error(e);}catch(IOExceptione){log.error(e);}}问题是只要不抛出异常,这种方法似乎就可以正常工作。将执行try块中的所有语句并关闭资源。但是您出于某种原因添加了try块。你调用了一个或多个可能抛出异常的方法,也可能你自己抛出异常。这意味着您可能还没有到达try块的末尾。因此,您不会关闭该资源。因此,您应该将所有清理代码放入finally块中,或者使用try-with-resource语句。使用finally模块与try块的最后几行相反,finally块总是被执行。这发生在成功执行try块之后或在catch块中处理了异常之后。因此,您可以确保清理所有打开的资源。publicvoidcloseResourceInFinally(){FileInputStreaminputStream=null;尝试{Filefile=newFile("./tmp.txt");inputStream=newFileInputStream(文件);//使用输入流读取文件}catch(FileNotFoundExceptione){log.error(e);}finally{if(inputStream!=null){try{inputStream.close();}catch(IOExceptione){log.error(e);}}}}Java7Try-With-Resource语句另一种选择是try-with-resource语句,我在IntroductiontoExceptionHandlinginJava中对此进行了详细描述。如果您的资源实现了AutoCloseable接口,您就可以使用它。这就是大多数Java标准资源所做的。当您在try子句中打开资源时,该资源会在try块执行或处理异常后自动关闭。publicvoidautomaticallyCloseResource(){Filefile=newFile("./tmp.txt");try(FileInputStreaminputStream=newFileInputStream(file);){//使用inputStream读取文件}catch(FileNotFoundExceptione){log.error(e);}catch(IOExceptione){log.error(e);}}二、指定具体的异常抛出的异常越具体越好。永远记住,不了解您的代码的同事,或者可能几个月后,将需要调用您的方法并处理该异常。因此,请务必向他们提供尽可能多的信息。这使您的API更易于理解。因此,您的方法的调用者将能够更好地处理异常,或者通过额外的检查来避免它。因此,请始终尝试找到最适合您的异常事件的类,例如抛出NumberFormatException而不是.IllegalArgumentException。并避免抛出未定义的异常。publicvoiddoNotDoThis()throwsException{...}publicvoiddoThis()throwsNumberFormatException{...}整理了2021年Java工程师经典面试题,共485页,约850道面试题及答案PDF,包括Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等几乎所有的技术栈,每个技术栈都有不少于50道经典面试题,不敢说刷完就进大厂,但是有针对性的刷一下,在面对面试官的时候多一点底气还是可以的。3.记录特定异常无论何时在方法签名中指定异常,它也应该记录在Javadoc中。这与之前的最佳实践具有相同的目标:为调用者提供尽可能多的信息,以便他可以避免或处理异常。因此,请确保在您的Javadoc中添加@throws声明并描述可能导致异常的条件。/***这个方法做了一些非常有用的事情...**@paraminput*@throwsMyBusinessExceptionif...happens*/publicvoiddoSomething(Stringinput)throwsMyBusinessException{...}四、当抛出异常时包含描述信息的最佳实践背后的想法与前两个相似。但这一次,您没有将信息提供给方法的调用者。每个必须知道在日志文件或监控工具中报告异常时发生了什么的人都可以阅读异常消息。因此,它应该尽可能准确地描述问题并提供最相关的信息以了解异常事件。不要误会我的意思;你不应该写一个段落。但是你应该用1-2个简短的句子解释为什么会出现异常。这有助于您的运营团队了解问题的严重性,还可以让您更轻松地分析任何服务事件。如果抛出一个特定的异常,它的类名很可能已经描述了错误的类型。因此,您不需要提供太多额外信息。一个很好的例子是NumberFormatException。当您以错误的格式提供String时,它由类java.lang.Long的构造函数抛出。try{newLong("xyz");}catch(NumberFormatExceptione){log.error(e);}NumberFormatException类的名称已经告诉您问题的类型。它的消息只需要提供导致问题的输入字符串。如果异常类的名称不那么具有表现力,则需要在消息中提供所需的信息。17:17:26,386错误TestExceptionHandling:52-java.lang.NumberFormatException:Forinputstring:"xyz"5.首先捕获最具体的异常大多数IDE可以通过最佳实践帮助您。当您尝试首先捕获不太具体的异常时,它们会报告无法访问的代码块。问题是只执行第一个匹配异常的catch块。因此,如果您先捕获IllegalArgumentException,您将永远无法到达应该处理更具体的NumberFormatException的catch块,因为它是IllegalArgumentException的子类。始终首先捕获最具体的异常类,然后将不太具体的catch块添加到列表的末尾。您可以在以下代码片段中看到此类try-catch语句的示例。第一个catch块处理所有NumberFormatExceptions,第二个catch块处理所有IllegalArgumentExceptions,它们不是NumberFormatExceptions。publicvoidcatchMostSpecificExceptionFirst(){try{doSomething("一条消息");}catch(NumberFormatExceptione){log.error(e);}catch(IllegalArgumentExceptione){log.error(e)}}六、不捕获Throwable异常Throwable是所有异常和错误的超类。您可以在catch子句中使用它,但绝对不要!如果在catch子句中使用Throwable,它不仅会捕获所有异常,还会捕获所有异常。它还捕获所有错误。JVM抛出应用程序未处理的严重错误问题。例如:OutOfMemoryError或StackOverflowError。两者都是由应用程序无法控制的情况引起的,无法处理。所以最好不要捕获Throwable除非你绝对确定你处于特殊情况下你能够或需要处理错误。publicvoiddoNotCatchThrowable(){try{//做点什么}catch(Throwablet){//不要这样做!}}七、不要忽略异常你分析过只在用例第一部分执行的错误报告吗?这通常是由被忽略的异常引起的。开发人员可能非常确定它不会被抛出,并添加一个不会处理或记录它的catch块。而且,当您找到该块时,您甚至可能会找到著名的“这永远不会发生”的注释之一。publicvoiddoNotIgnoreExceptions(){try{//做某事}catch(NumberFormatExceptione){//这永远不会发生}}那么,您可能正在分析一个永远不会发生的问题。所以不要忽略异常。您不知道您的代码将来会如何变化。有人可能会删除阻止异常事件的验证,而不会意识到它会导致问题。或者,抛出异常的代码已更改,现在抛出同一类的多个异常,并且调用代码不会阻止所有异常。您至少应该写一条日志消息,告诉每个人刚刚发生了不可想象的事情,需要有人检查一下。publicvoidlogAnException(){try{//做某事}catch(NumberFormatExceptione){log.error("这不应该发生:"+e);}}8.不要记录和抛出经常被忽视的最佳实践。您可以找到许多代码片段,甚至是捕获、记录和重新抛出异常的库。尝试{newLong("xyz");}catch(NumberFormatExceptione){log.error(e);throwe;}记录发生的异常,然后重新抛出它,以便调用者可以适当地处理它,可能是直观的。但是它会为同一个异常写入多条错误信息。17:44:28,945错误TestExceptionHandling:65-java.lang.NumberFormatException:对于输入字符串:线程“main”中的“xyz”异常java.lang.NumberFormatException:对于输入字符串:java.lang.NumberFormatException.forInputString中的“xyz”(NumberFormatException.java:65)atjava.lang.Long.parseLong(Long.java:589)atjava.lang.Long.(Long.java:965)atcom.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)atcom.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)atcom.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)其他消息也不添加任何信息。如最佳实践4中所述,异常消息应描述异常事件。堆栈跟踪告诉您异常是在哪个类、方法和行中抛出的。如果您需要添加额外的信息,您应该捕获异常并将其包装在自定义异常中。但一定要遵循最佳实践9.publicvoidwrapException(Stringinput)throwsMyBusinessException{try{//dosomething}catch(NumberFormatExceptione){thrownewMyBusinessException("Amessagethatdescribestheerror.",e);}}所以只有当你想处理它时才能捕获异常。否则,在方法签名中指定它并让调用者处理它。9.包装异常而不使用它们有时捕获标准异常并将其包装在自定义异常中会更好。此类异常的典型示例是特定于应用程序或框架的业务异常。这允许您添加额外的信息,还可以实现对异常类的特殊处理。执行此操作时,请确保将原始异常设置为原因。异常类提供了一个接受特定构造函数作为参数的Throwable。否则,您将丢失堆栈跟踪和原始异常的消息,这将难以分析导致异常的异常事件。publicvoidwrapException(Stringinput)throwsMyBusinessException{try{//做某事}catch(NumberFormatExceptione){thrownewMyBusinessException("描述错误的消息。",e);或者在捕获异常时,您应该考虑很多不同的事情。他们中的大多数人的目标是提高代码的可读性或API的可用性。异常通常既是一种错误处理机制,也是一种通信媒介。因此,您应该确保与您的同事讨论要应用的最佳实践和规则,以便每个人都能理解一般概念并以相同的方式使用它们。
