当前位置: 首页 > 后端技术 > Java

Java异常处理机制

时间:2023-04-01 18:23:41 Java

我们不能画一个完美的圆,我们不能追求绝对的美。--罗翔我们不能创造一个没有bug的程序世界。程序的世界并不完美,总有意想不到的情况让程序无法完成使命。因此,提供一套处理异常的机制可以最大程度的保证程序的可靠性。java中的异常处理机制可以从Exception和Error开始。1.异常和错误什么是异常和错误?这件事要从Throwable说起。Throwable在java.lang包中,是Java中所有异常和错误的超类。Exception和Error都继承了Throwable。使用throw、catch等关键字时,会判断是否是Throwable的子类。其中,Exception通常是指程序可以预见的意外情况,常见的NPE属于此类;相应地,Error指的是不可预知的困难情况,通常是程序所在环境(JVM)的严重问题,比如众所周知的OOM。这些错误类型之间有什么区别?待遇一样吗?2.Checkedvsunchecked所有的异常和错误都可以分为checked异常和unchecked异常。对于异常场景,越早处理,通常获益越大。如果能在编译时检测到,比在运行时处理的好处更大。CheckedException是编译时可以检测到的异常,比如IOException。当操作文件时,编译器会提醒你处理异常。这里,FileNotFoundException是检测到的IO异常,继承自IOException。publicvoidtestFile(StringfileName){try{BufferedReaderin=newBufferedReader(newFileReader(fileName));}catch(FileNotFoundExceptione){e.printStackTrace();但是,编译时能提示的异常总是有限的,大部分不能有效提示,比如NullPointerException。所以Error和RuntimeException类统称为非检查异常,写的时候无法检测和提示,而其他的都是检查异常。CheckedException有点像免责声明,提醒用户在使用时存在一定的风险,并给出风险类型,以便用户做出应对措施。非检查型是一种无法提前预知的风险,不知道会发生什么,也不知道什么时候发生。总之,这两类维度,从不同的阶段,分为checked和non-checked,这样可以有不同的处理方式,并且从重要性的角度,分为exception和errors来表达其严重程度.那么当真正发生异常时,JVM是如何处理这些异常的呢?3.处理机制简单的说就是用一张异常表来管理异常,记录了监控到的代码块,对应的异常类型,以及异常发生时跳转到的目标代码(其实就是goto语句).如下图,表示0~7发生ClassNotFoundException时,跳转到17处理,发生Exception时,跳转到36处理。这里有一个很重要的知识点,就是最后是怎么体现的?如何保证finally的代码能够执行呢?编译时将finally中的代码复制到其他代码块中,放在代码块return之前。比如下面的例子,经过编译,在字节码层面类似于第二种形式。publicvoidtest(Stringname){try{Class.forName(name);}catch(ClassNotFoundExceptione){System.out.println("catchClassNotFoundException");}catch(Exceptione){System.out.println("catchException");}finally{System.out.println("最后代码");}}publicvoidtest(Stringname){try{Class.forName(name);System.out.println("最后代码");}catch(ClassNotFoundExceptione){System.out.println("catchClassNotFoundException");System.out.println("最后代码");}catch(Exceptione){System.out.println("catchException");System.out.println("最后代码");所以这里有个奇怪的地方,如果return写在finally里,会导致其他代码块里的return和throw无效,所以要养成好习惯,避免在finally里写return,throw等中断语句,不然就很难理解了,也不知道是怎么回事。值得一提的是,在java7之后,引入了try-with-resource这个语法糖。我们来看一个文件操作的例子,实例化输入输出流,在finally中需要非常复杂的关闭连接的处理。publicvoidtestFile(StringfileInputName,StringfileOutputName){BufferedInputStreambis=null;BufferedOutputStreambos=null;try{bis=newBufferedInputStream(newFileInputStream(fileInputName));bos=newBufferedOutputStream(newFileOutputStream)(fileOutput)(Output)FileNotFoundExceptione){e.printStackTrace();}finally{if(bis!=null){try{bis.close();}catch(IOExceptione){e.printStackTrace();}finally{if(bos!=null){try{bos.close();}catch(IOExceptione){e.printStackTrace();}}}}}}try-with-resources只需要这样写,非常简洁优雅,try中写的资源,当出现异常时,应用会自动关闭这样资源连接和写法还有一个好处。在上面的例子中,如果finally中的代码出现了异常,原来的异常会被覆盖,但是新的写法不会,而是会通过addSuppressed将关闭连接的异常添加到原来的异常里面。publicvoidtestFile(StringfileInputName,StringfileOutputName){try(BufferedInputStreambis=newBufferedInputStream(newFileInputStream(fileInputName));BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream(fileatchOutputName))){//todo}cute.printStackTrace();这里有几个重点知识点,一个是异常表,有兴趣的同学可以看看编译后的字节码文件,一个是finally的实现原理,本质上就是copycode,理解了这个之后,很多相关的面试题就可以了很容易解决。最后是一种新的写法,让资源管理更优雅,解决了异常抑制的问题,让问题出现后排查更高效。4.经典例子了解了基础知识后,分析ClassNotFoundException和NoClassDefFoundError的经典例子,加深理解。ClassNotFoundException是一个已检查的异常。写Class.forName()时,会提示异常处理。当JVM加载类文件到方法区时,如果没有找到该类,就会抛出这个异常。一般是输入的名字有问题造成的。NoClassDefFoundError是一个Error,一般是新建对象时没有找到类的定义引起的,即编译时文件存在,运行时找不到。会出现这种情况,一般是因为环境问题,比如编译出来的jar损坏等因素。5.应用原则编写业务代码时,有哪些指导原则?接下来介绍一些比较重要的原理。最值得一提的是Throwearly,catchlate原则,具有广泛的适用性。首先,throw应该越早越好。比如读取文件时,提前判断文件名是否为空,尽快结束进程。对比下面两种写法,第二种方法可以更快定位问题,而第一种方法使用堆栈信息。可能很长,一眼就能看出问题所在。publicvoidtest(StringclassName){try{Class.forName(className);}catch(ClassNotFoundExceptione){e.printStackTrace();}}publicvoidtest(StringclassName){try{if(className==null){thrownewNullPointerException();}Class.forName(类名);}catch(ClassNotFoundExceptione){e.printStackTrace();}}这种思路也可以应用到编写业务代码中,可以提前进行参数校验,业务标定测试,然后实现具体的业务行为。生活中更是如此,出门前先查好“技能金”,避免上地铁没带手机的尴尬。其次,catchlater表示捕获到异常后,如果不知道如何处理,则将其抛给上层,而不是将异常吞掉或随便处理。因为在更高的层次上,会有更多的信息来处理异常。比如,在市里办不了的案子,就应该在省里办。说到catch之后的处理,这里有几点需要注意:1.异常不要吞掉,处理不了就扔掉;2、不要调用e.printStackTrace(),因为它是标准错误输出;3.不要捕获Exception或throwable;4、注意多个catch类型的顺序,从小到大依次处理。此外,不要将异常用于业务流程控制。虽然可以达到目的,但是可读性不高,维护效率低,另外就是运行效率也低,因为try-catch需要额外的开销,比ifelse的判断效率低很多,并且每次实例化一个Exception,都会对栈进行快照,这是一个很重的动作。6.总结说了这么多,总的来说,java的异常处理机制有哪些优势呢?首先,想象一下如果没有这个机制会发生什么。比如C语言,就没有专门的异常处理机制。异常反映在返回值中。这种做法会让异常处理逻辑侵入业务代码,降低代码的可读性。性别。其次,如果不对异常进行封装,对用户来说是极其不友好的,代码专业的报错会让用户看不懂,降低产品体验。7.扩展最后留几个思考问题:1.在业务开发中,是否需要定义检查异常?为什么?2.如何处理像lambda这样的反应式编程中的异常?3、在业务开发中,使用拦截器统一拦截接口的异常进行处理。这种方法是好是坏?