前言Java中如何处理异常,这个话题看似简单,不就是try...catch吗,但往往bug更容易出现在一些我们更容易忽略的简单地方。大多数成熟的开发团队都有一套关于如何处理异常的规范和最佳实践。在本期中,我整理了我的团队正在使用的9个最佳实践,希望对您的异常处理有所帮助。1.使用finally或者try...with...resource来关闭资源如果我们需要在try代码块中使用一些资源,比如InputStream,我们需要在使用完后关闭这些资源。这是一个错误示例publicvoidtryResource(){FileInputStreaminputStream=null;try{Filefile=newFile("./小黑说Java.txt");inputStream=newFileInputStream(文件);//使用inputStream读取文件//不要这样做inputStream.close();}catch(FileNotFoundExceptione){log.error("Filenotfound",e);}catch(IOExceptione){log.error("Filereadexception",e);}}复制代码在上面的代码中,只要读取文件时没有异常,这段代码就可以正常工作,但是只要在close()中抛出异常try块中的方法,资源不会被关闭。所以在这种情况下我们应该将资源关闭代码放在finally中或者使用try...with...resource语句。使用finallyfinally块中的代码无论是否发生异常都会被执行,所以可以保证资源对象被关闭。publicvoidcloseResourceInFinally(){FileInputStreaminputStream=null;try{Filefile=newFile("./小黑说Java.txt");inputStream=newFileInputStream(文件);//使用inputStream读取文件}catch(FileNotFoundExceptione){log.error("Filenotfound",e);}finally{if(inputStream!=null){try{inputStream.close();}catch(IOExceptione){log.error("资源关闭异常",??e);}}}}copythecodeusingtry...with...resource如果你使用的JDK版本是1.7+,你也可以选择使用try...with...resource语句。如果您使用的资源类实现了AutoCloseable接口,则可以使用此方法。Java中的大多数标准资源类API都实现了这个接口。在try子句中打开资源会在执行完try代码块或处理异常后自动关闭资源对象。publicvoiduseTryWithResource(){Filefile=newFile("./小黑说Java.txt");try(FileInputStreaminputStream=newFileInputStream(file);){//使用inputStream读取文件}catch(FileNotFoundExceptione){log.error("Filenotfound",e);}catch(IOExceptione){log.error("Filereadexception",e);}}复制代码2.如果我们的方法是需要抛异常,异常类型越具体越好。因为在外面调用你的代码的其他人可能并不清楚你内部的实现逻辑,所以一定要尽可能多的给他提供信息,让别人在使用你的方法时更容易理解,从而调用者可以更好的处理抛出的异常。比如在你的方法内容中抛出NumberFormatException,会比抛出IllegalArgumentException或者直接抛出Exception有更明确的意义。3.在方法注释中解释异常如果你的方法声明了可能抛出异常,那么应该在方法的文档注释中解释异常。这个和上一个的目的是一样的,就是让方法的调用者提前获得更多的信息,这样他在调用你的方法的时候就可以避免出现异常,或者更清楚如何处理异常。因此,我们应该在方法的文档注释中加入@throws语句,说明在什么情况下会抛出相应的异常。/**这个方法内部做了什么...*@paraminput@throwsBusinessException如果发生xxx,会抛出这个异常*/publicvoiddoSomething(Stringinput)throwsBusinessException{...}4.携带足够的描述信息intheexception类似于前两种方法的目的。在异常中携带足够的描述信息的目的是为了在异常发生时查看日志文件中的异常信息时看到更多有用的信息。所以我们应该尽可能准确的描述为什么会抛出这个异常,并提供最相关的数据信息给其他人定位。当然,这里也不能太极端。小作文如果写得洋洋洒洒,就应该用一小段信息描述,让运维同事明白问题的严重性,更容易分析问题。不需要提供一堆多余的冗余信息,尽量准确。例如,如果在创建Long对象时传入一个字符串,则会抛出NumberFormatException。publicstaticvoidtestLong(){try{Longabc=newLong("ABC");}catch(NumberFormatExceptione){log.error("Formatexception",e);}}复制代码NumberFormatException的类名已经告诉我们有一个数字格式异常,所以只需要在消息中提供输入字符串。如果你定义的异常类名不能明确表达异常是什么,比如BusinessException,你应该在消息中表达更多的信息。5.首先捕获更具体的异常。一般在我们使用的IDE中,如果在捕获异常时先捕获Exception等不太具体的异常,再捕获IOException等比较具体的异常,后面会提示。无法到达catch块。所以我们应该先捕获最具体的异常类,最后捕获不太具体的异常类。publicvoidcatchExceptions(){try{doSomething("小黑说Java");}catch(NumberFormatExceptione){log.error("格式异常",e);}catch(IllegalArgumentExceptione){log.error("非法参数",e);}}复制代码6.不要捕获ThrowableThrowable是所有Exception和Error的父类。虽然可以在catch块中捕获它,但我们不应该这样做。因为如果使用Throwable的话,不仅所有抛出的Exception都会被捕获,所有的Error也会被捕获。当我们的程序抛出Error时,就意味着严重的无法处理的问题,比如典型的OutofMemoryError、StackOverflowError等,这两种Error都是由程序无法控制和处理的情况引起的。所以,最好不要在你的catch中捕获Throwable,除非你非常确定try块中的代码抛出了可以处理的异常。publicvoidcatchThrowable(){try{//一些业务代码}catch(Throwablet){//不要这样做}}复制代码7.不要忽略异常你可能非常确定当你开发,确实在开发的时候没有抛出异常,所以你在catch块中没有做任何处理异常的事情。publicvoiddoNotIgnoreExceptions(){try{//一些业务代码}catch(NumberFormatExceptione){//认为它永远不会在这里执行}}重复代码但是,你实际上不确定将来是否有人会在你的try块中在添加新的代码,而他可能没有意识到他添加的代码会导致抛出异常,这会导致异常实际发生在线路上,但没有人知道。所以,你至少应该在catch中打印一行日志,告诉运维同事,“报警,这里有不能发生的异常”。publicvoiddoNotIgnoreExceptions(){try{//一些业务代码}catch(NumberFormatExceptione){log.error("Alert,这里有一个不可能的异常",e);}}复制代码8,不打印抛出异常之后的日志可能是大多数人都会犯的。我看过很多别人的代码在处理异常的时候先打印一行异常日志,然后抛出异常,或者变成RuntimeException抛出。出去。它甚至出现在一些开源框架中。publicvoidtestCatchEx(){try{newLong("heihei");}catch(NumberFormatExceptione){log.error("NumberFormatException",e);扔e;直观来说,没有什么问题,让调用你方法的人来处理就好了。但是这样一来,对于抛出的异常,日志中会打印多条错误信息。重复的日志不会带来任何有价值的信息。参考上面第4项的描述,异常信息应该携带足够的信息并且是准确的。如果你需要添加其他信息,你应该在抛出之前将捕获的异常封装在你的自定义异常中。publicvoidwrapException(Stringinput)throwsBusinessException{try{//dosomething}catch(NumberFormatExceptione){thrownewBusinessException("Adescriptionoftheexception.",e);}}所以,我们应该只有当你想要的时候处理异常,应该catch,否则应该抛出,并在方法前说明,让调用者处理。9.异常封装时使用原生异常通常在项目开发过程中,会有一套自定义异常,用于将API中的标准异常封装成自定义异常,可以用来在外部做一些统一的异常处理层。但是我们在使用自定义异常封装原始异常的时候,需要保证在自定义异常中保存了原始异常作为原因,否则你会在外层丢失原始异常的stacktrace信息,你会无法分析异常信息。抛出异常的具体原因。publicvoidwrapException(Stringinput)throwsBusinessException{try{//dosomething}catch(NumberFormatExceptione){//在构造参数中使用e作为原因thrownewMyBusinessException("Adescriptionoftheexception.",e);}}CopycodeSummary在抛出或捕获异常时,我们应该考虑很多不同的事情,其中??大部分都是上面提到的,以提高代码的可读性并使API提供给其他人更容易使用。通常,异常不仅是一种错误处理机制,还具有一定的信息媒介作用。我们应该遵循这些异常处理规则和最佳实践,写出更规范的代码,不会让别人抱怨。
