javaweb开发中遇到的问题及解决方案(Java Web轻松学51 - Java异常基础)java基础 / Java Web开发中的异常处理...

wufei123 发布于 2024-07-02 阅读(6)

本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人目录介绍异常的本质Java异常的设计Java异常的发生Java异常的检测。

Java异常对象的生成(实例化)Java异常对象的抛出Java异常对象的捕获和处理(异常处理器的定义)总结介绍前面这篇文章介绍JDBC初步使用的时候,我们提到了相关的异常现在,我们就来介绍一下Java异常的基础知识,一来以后遇到类似XXXException、throws、throw、try-catch-finally等关键字时能够知道是什么意思;二来我们可以用它来处理或设计我们应用程序中出现的异常。

在某种程度上,可以说编写程序的主要功夫都是花费在异常的设计和处理上异常的本质异常这个词实际上是由英文单词 exception 翻译过来的,其含义是:n.一般情况以外的人(或事物); 例外; 规则的例外; 例外的事物;。

什么是例外?就是正常的情况之外那什么是正常的情况?可以这么说,在我们程序执行的过程中,你期望它执行的指令流都属于正常的情况这样还是很抽象,举个例子,我们的程序是运行在计算机这个设备上的,那么设备肯定会有损坏的时候吧,CPU、内存、硬盘、网卡等等设备都有可能会坏掉,本来程序运行着好好的,突然某个设备坏掉了,这就是一种正常情况之外的。

事件再比如,本来程序运行着好好的,这时突然某个设备要求马上占用CPU,那么CPU就要中断正在运行中的程序,去处理该设备的请求,这也是一种正常情况之外的事件又比如,本来程序运行着好好的,操作系统或者Java虚拟机的运行出现了。

故障,那程序是肯定没法继续运行下去了,这也是一种正常情况之外的事件又比如,本来程序要读取某个文件中的数据,正常来说,该文件是应该存在的,而谁也不能保证该文件就不会被其他外部力量删除啊,所以,读取文件时遇到文件不存在,或被损坏,或是没有读取权限,或是啥啥啥,这也是一种正常情况之外的。

事件又比如,程序需要执行整数除法,正常情况是不会除以零的,但谁能保证就一定不会出现这种情况呢,CPU是不能执行除零这种运算的,这也是一种正常情况之外的事件又比如,你要执行某个对象的方法,而不小心传入的是空(null)引用,这就是著名的。

NPE(NullPointerException),显然,这是由于我们的错误的使用API造成的,我们不该传入一个空引用,这也是一种正常情况之外的事件又比如,一个数组或列表对象,明明没有那么多元素,你却不小心访问超过数组或列表的大小之外的位置,这就是著名的。

越界异常,这也是一种正常情况之外的事件又比如,程序的正常逻辑是希望传入合适的值,或者合适格式的数据,但谁也不能保证就一定能够传入合适的值,或者合适格式的数据啊,这也是一种正常情况之外的事件所以,异常的本质就是正常情况之外的事件,当然,你也可以自定义哪些是正常哪些是异常。

当然,有的异常发生后,程序也是无能为力的,比如设备、操作系统、JVM坏了,程序也就随之停止执行了,又如何能够得知并处理它呢而有的异常发生后,程序可以捕获它,然后向用户报告发生了什么异常,让用户做出正确选择;或者程序自己处理该异常,自动恢复执行。

Java异常的设计在Java中,一切都是对象,所以,异常也被设计为对象,这就是异常对象。当然,每一个异常对象都必然属于某个类型,这就是异常类。Java异常类的层次结构如下:

图片来自Java官方文档每一个Java异常对象,都包含有关于这个异常的信息,比如,这个异常是在执行到哪个类的哪个方法的哪行代码时发生的;当然方法的调用会形成调用栈(即一个方法中又会调用自己或其他的方法),所以整个调用栈的信息也是很重要的,以后再细说;还有,有时候一个异常是由另一个异常引起的,这又会形成异常链,这也是很重要的信息。

Error类:就是下面所说的严重的异常,一般是我们程序的外部发生的;Exception类:一般是我们程序的内部发生的,又分为RuntimeException类和其他类;RuntimeException类:是属于内部异常,但通常也是不可预料和不可恢复的,通常预示着程序有某种bug,比如逻辑错误或者API的错误使用等。

其中,Error和RuntimeException都属于不可预料和不可恢复的,统称为 unchecked exceptions (可以翻译为非受检异常),剩下的自然就是受检异常了为什么叫受检和非受检呢?从形式上看,一旦一个方法内部可能会抛出受检异常,就必须。

检查它:使用 try-catch 块定义异常处理器来处理该异常;或者在该方法的声明时使用 throws 关键字指明该方法不处理该异常,进一步抛出该方法的调用者,由JVM去检测该调用者是否处理该异常否则,Java编译器会编译报错!。

我们通常设计自己的异常类时,首先必须处于上面Throwable类层次结构中,但一般都会设计为继承Exception类或其子类Java异常的发生异常的种类很多,但从发生来源看,就只有两种:我们程序外部产生的,比如硬件设备、操作系统、Java虚拟机等,通常,这种异常被封装成

Error类及其子类,从类名就可以看出这不仅仅是一个异常,更是一个错误,比起异常来更加严重的,一般也是我们不能预料的,不能恢复的;我们程序内部产生的,即执行我们的程序代码所产生的异常,它们被Java设计为

Exception类及其子类所以说,异常这个名字起的有点不太合适,或者说把Exception作为父类的名字再设计Error和XXXException作为它的子类更合适一点不管怎样,我们还是适应现状吧Java异常的检测

不管是Error,还是Exception,我们的系统必须要检测到它的发生因为Java程序是运行在JVM中的,显然JVM可以检测到异常的发生,比如,IO异常、除零、空指针、越界异常等都可以被JVM检测到当然,

我们的程序也可以检测异常,甚至是上面的异常,比如,在读取文件时可以先判断文件是否存在、做整数除法时先判断除数是否为零、访问某对象的方法时先判断该引用是否为空、访问数组或列表时先判断索引是否越界等不过,我们显然不必多此一举,上面的异常都太普遍,程序的任何地方都可能会发生,如果每一处都要我们检测,那我们的代码就到处充斥着检测的代码。

我们的程序通常检测的是我们自定义的不具备普遍性的异常,比如指定某些变量必须是业务逻辑范围内的,某些数据的格式必须是我们指定的格式等等所以,通常检测代码都是判断一些正常的条件是否没有满足,或者一些异常的条件是否满足。

JVM中检测异常的代码我们自然是看不到了(实际上也能够看到,那就是查看JVM的实现代码),不过肯定是在执行Java程序的地方增加了检测异常的代码所以,我们在编写Java程序的时候,不需要增加任何代码,JVM也能够检测到那些普遍性的异常。

,比如:public void m(Object obj) { obj.toString(); }上面代码中,一旦在调用该方法时传入一个null值,JVM在执行 obj.toString() 自然会检测到空指针异常(NPE)。

当然,JVM除了检测异常,还会生成异常对象并抛出,捕获该异常对象,寻找一个匹配的异常处理器(如果找不到则使用默认的异常处理器),执行异常处理器的代码(处理异常)等Java异常对象的生成(实例化)既然Java异常的发生可以由JVM和我们的应用程序所检测到,那Java异常对象的生成自然也就由它们来生成了。

JVM生成异常对象的方式不敢说就是使用 new 操作,但估计也是类似,不过可能使用的是原始的JVM指令我们的应用程序生成异常对象的方式,与生成普通对象的方式一样,可以使用 new 操作:XXXException ex = new XXXException(parameters);。

Java异常对象的抛出Java为我们设计了一种异常对象的抛出机制既然是抛出,那是谁抛出呢?显然是发生异常的那句代码抛出啊,不过,Java代码都是组织成类中的方法,所以也可以说是包含发生异常的那句代码的方法抛出异常

事实上,Java语言和JVM就是这么设计的,一个方法可以声明抛出某个异常(受检和非受检均可,但方法内如果有未定义异常处理器的受检异常,则必须声明),使用的是 throws 关键字,像下面这样:public void m() throws XXXException, YYYException, ...。

当然,可以声明抛出多个异常,以逗号分隔即可既然声明一个方法可能抛出异常,那么该方法肯定是包含可能发生异常的代码如果该代码由JVM检测并生成的异常对象,当然就由它来抛出了,我们不必深究,比如:public void m(Object obj) { obj.toString();//可以理解为,这里由JVM隐式的检测异常、生成异常对象并抛出了 //下面是其他代码,如果上面抛出异常,则不会执行了 }。

如果是我们的应用程序中检测到某种异常并生成异常对象,就由我们来显式的抛出,使用Java提供给我们的 throw 语句:if (发生异常) { throw new XXXException(); } //下面是其他代码,如果上面抛出异常,则不会执行了

上面的代码包括了检测异常的发生、异常对象的生成并抛出那又是抛给谁呢?显然是抛给JVM啊,一旦抛出异常对象,后面的代码就不会再执行了,JVM会拿该异常对象去寻找匹配的异常处理器了所以,抛出机制本质上是一种(CPU的)。

控制权的转移,是在我们的程序中各个方法和JVM之间转移,转移的目的是寻找到匹配的异常处理器,而转移的数据就是那个异常对象!Java异常对象的捕获和处理(异常处理器的定义)既然Java异常对象是抛给JVM,显然JVM就

捕获到了这个异常对象,但这个捕获显然很不够,必须将该异常对象交给某段代码来处理才行这个某段代码就是异常处理器Java为我们提供了定义异常处理器的 try-catch-finally 块语法:try { //这里是可能抛出异常的代码 ... obj.m(); //这里可能隐式的抛出NPE,或者m()方法声明会抛出某种异常 if (发生异常) { throw new XXXException(); //这里显式的抛出自定义的或第三方库提供的异常 } //后面的代码 ... } catch (XXXException e) { //这里就定义了一个异常处理器,它匹配抛出的异常类型是XXXException } catch (YYYException e) { //这里又定义了一个异常处理器,它匹配抛出的异常类型是YYYException } finally { //这里的代码无论try块中是否抛出异常,都会执行,除非JVM退出或执行try块的线程销毁 }。

try-catch-finally 块语法必须至少包括一个catch语句或一个finally语句,可以包括多个catch语句需要注意的是,finally块并没有定义一个异常处理器,它是无论是否发生异常都会执行的代码,一般是用于编写释放资源的代码。

事实上,异常对象被抛出后,JVM就获得了控制权,它会寻找匹配的异常处理器,首先会在本方法中寻找,如果没有,就到该方法的调用者内去寻找,一直找到最顶层方法为止,如果最终都没有找到,那就会交给默认的异常处理器。

所以可以这么说,最后是某个异常处理器捕获到了该异常对象总结所谓异常,就是正常情况之外的事件,异常可以是系统引发的,也可以是业务自定义的;异常,从生命周期思维看,会经历发生、检测、处理(此阶段在Java中又分为异常对象的生成、抛出、捕获和处理)等阶段;。

Java里面将异常封装成类,形成类层次结构,顶层父类是Throwable;Java中的异常分为三种:Error及其子类、RuntimeException及其子类、其他Exception及其子类,最后一种是受检异常,方法内必须为它定义异常处理器或方法中声明要抛出它;前两种是非受检异常,往往是严重的、普遍存在的、不可预料且不可恢复的;

JVM和我们的应用程序都可以进行异常的检测、异常对象的生成、异常对象的抛出、异常对象的捕获和处理(异常处理器定义、匹配、执行);异常对象的生成可以使用 new 操作符,与普通对象的实例化无异;异常对象的抛出在方法声明中可以使用 throws 关键字;在方法内部可以使用 throw 关键字;

异常处理器的定义使用 try-catch-finally 块语法,每一个catch就是定义了一个异常处理器;异常处理器的匹配和执行是由JVM执行的,匹配主要是依据异常的类型,在方法的调用栈中依次寻找匹配的异常处理器;

异常可以形成异常链,即异常处理器捕获到一个异常后,生成另一个异常继续抛出;异常的运行机制可以看作是一种控制权的转移机制(事实上,所有应用程序与系统之间也是如此),在Java中则是(CPU的)控制权在应用程序与JVM之间转移,我们可以把(运行中的)JVM想象成是一个人(实际上应该把CPU比作一个人,但抽象是可以有层级的,这样也更易理解一些),Java程序则是一条条指令,这个人在不断的读取指令并执行,这些指令肯定有一定的语法,碰到某些指令就得执行某些动作:

这个人先寻找main方法,每遇到一个方法的调用,就会去寻找该方法的代码,并记录到方法调用栈;每遇到一个try-catch块就记录该方法定义了一个异常处理器;一旦某个方法内抛出异常,就寻找匹配的异常处理器;该方法没有匹配的异常处理器就继续往它的调用者方法寻找,直到找到有匹配的异常处理器,并执行该异常处理器的代码,然后继续从该异常处理器之后的代码开始执行。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

河南中青旅行社综合资讯 奇遇综合资讯 盛世蓟州综合资讯 综合资讯 游戏百科综合资讯 新闻67899