当前位置: 首页 > 科技观察

深入研究分析Java异常

时间:2023-03-21 22:04:50 科技观察

前言本文是异常内容的集大成者,力求对异常知识进行全面深入的研究分析。本文由金丝燕网独家撰写,参考了众多网络资源,经过内容识别、文本格式验证等步骤编辑而成,供广大读者阅读。对于本文内容,建议新手需要多思考,努力掌握,老手只需温故而知新。本文内容属于基础知识研究范畴。不要以为看了这篇文章,就可以掌握异常的知识。切记:打千曲知音,看千剑知兵器,所以我想如果没有大量的源码阅读经验,你很难知道什么时候需要自定义异常,什么时候需要自定义异常。你需要抛出异常。异常机制概述异常机制是指当程序出现错误时,程序如何处理。具体来说,异常机制为程序退出提供了安全通道。当错误发生时,程序执行的流程发生变化,程序的控制权转移到异常处理程序。异常处理的流程当程序抛出异常时,程序跳出程序中引起异常的代码,java虚拟机检测并搜索匹配try关键字的catch块来处理异常.如果发现,则将控制权转移到catch块中的代码,然后继续执行程序,try块中的异常代码将不再重新执行。如果没有找到处理异常的catch块,执行完所有finally块代码,调用当前线程所属ThreadGroup的uncaughtException方法后,终止当前遇到异常的线程。异常结构异常继承结构:Throwable是基类,Error和Exception继承Throwable,RuntimeException和IOException继承Exception。Error和RuntimeException及其子类成为非检查异常(unchecked),其他异常成为检查异常(checked)。ErrorAbnormalError表示程序在运行过程中发生了非常严重且不可恢复的错误。在这种情况下,应用程序只能停止运行,比如JAVA虚拟机出错。Error是一种uncheckedException,编译器不会检查Error是否被处理,程序中也没有必要捕获Error类型的异常。一般来说,程序中不应该抛出Error类型的异常。RuntimeExceptionException异常包括RuntimeException异常和其他非RuntimeException异常。RuntimeException是一种UncheckedException,意思是编译器不会去检查程序是否处理了RuntimeException,也没有必要在程序中捕获RuntimeException类型的异常,也没有必要在程序中声明并抛出RuntimeException类方法体。当RuntimeException出现时,意味着程序中出现了编程错误,所以应该找出错误并修改程序,而不是去捕获RuntimeException。CheckedExceptionCheckedException是编程中使用最多的Exception。所有继承自Exception且不是RuntimeException的异常都是checkedException,比如上图中的IOException和ClassNotFoundException。JAVA语言规定必须处理checkedException,由编译器检查。要么在方法体中声明抛出的checkedException,要么使用catch语句捕获checkedException进行处理,否则编译无法通过。声明方法时抛出异常语法:throws(略)声明方法时为什么要抛出异常?方法是否抛出异常与方法返回值的类型一样重要。假设一个方法抛出异常但没有声明该方法会抛出异常,那么客户端程序员可以调用这个方法而不需要编写代码来处理异常。那么,一旦发生异常,就没有合适的异常控制器来解决这个异常。为什么抛出的异常一定是checkedexception?RuntimeException和Error可以在任何代码中产生,不需要程序员显式抛出,一旦发生错误,就会自动抛出对应的异常。遇到Error,程序员一般是无能为力;遇到RuntimeException,一定是程序有逻辑错误,必须修改程序;只有checkedexceptions才是程序员关心的,程序应该也应该只抛出或处理Checkedexception。checkedexception被程序员抛出,分为两种情况:客户端程序员调用库函数抛出异常;客户端程序员使用throw语句来抛出异常。注意:重写父类方法的子类方法不能抛出比父类方法更多的异常。因此,有时在设计父类的方法时,声明抛出异常,但实际实现该方法的代码并没有抛出异常,这样做的目的是为了让子类方法重写时更容易抛出异常父类方法。Howtothrowanexceptionsyntaxinthemethod:whatexceptiondoesthrow(slightly)抛出?对于一个异常对象,真正有用的信息是异常对象的类型,异常对象本身是没有意义的。比如一个异常对象的类型是ClassCastException,那么类名就是最有用的信息。因此,在选择抛出什么异常时,最重要的是选择类名能够清楚说明异常的类。一个异常对象通常有两个构造函数:一个是无参构造函数;另一种是带有字符串的构造函数,它将作为异常对象除类型名称之外的额外描述。为什么要创建自己的异常?当Java内置的异常不能清楚说明异常情况时,就需要自己创建异常。需要注意的是,最有用的信息是类型名,所以不要把精力花在异常类的设计上。throw和throws的区别publicclassTestThrow{publicstaticvoidmain(String[]args){try{//调用有throws声明的方法,异常必须显式捕获//否则必须在main方法中再次声明throwthrowChecked(-3);}catch(Exceptione){System.out.println(e.getMessage());}//调用抛出Runtime异常的方法可以显式捕获异常,//或者忽略异常throwRuntime(3);}publicstaticvoidthrowChecked(inta)throwsException{if(a>0){//自己抛出异常//代码必须在try块中,或者在有throws语句的方法中thrownewException("a的值大于0,不符合要求Requirement");}}publicstaticvoidthrowRuntime(inta){if(a>0){//自己抛出RuntimeException,可以显式捕获异常//或者完全忽略异常,并通过该方法的异常调用者处理thrownewRuntimeException("a的值大于0,不满足要求");}}}补充:throwChecked函数的另一种写法如下:publicstaticvoidthrowChecked(inta){if(a>0){//自己抛出Exception异常//代码必须在try块中,或者在有throws语句的方法中try{thrownewException("a的值大于0,不满足要求");}catch(Exceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}}}注意:此时main函数中的throwChecked不需要尝试异常。异常应该在声明的方法中抛出还是在方法中捕获?处理原则:捕获并处理你知道如何处理的异常,传递你不知道如何处理的异常使用finally块释放资源finally关键字确保无论程序如何使用任何方法离开try块,finally中的语句会被执行。以下三种情况会进入finally块:(1)try块中的代码正常执行。(2)在try块中抛出异常。(3)在try块中执行return、break、continue。所以当你需要一个地方来执行在任何情况下都必须执行的代码时,你可以将这段代码放在finally块中。当你的程序使用外部资源,如数据库连接、文件等时,你必须在finally块中编写释放这些资源的代码。必须注意的是:finally块中不能抛出异常。JAVA异常处理机制保证在任何情况下都必须先执行finally块,然后离开try块。因此,当try块出现异常时,JAVA虚拟机首先去finally块执行finally块中的代码,finally块执行完毕。之后,抛出一个异常。如果在finally块中抛出异常,则不能抛出try块捕获的异常。外面捕获到的异常是finally块中的异常信息,真正发生在try块中的异常堆栈信息丢失了。请看下面代码:Connectioncon=null;try{con=dataSource.getConnection();...}catch(SQLExceptione){...throwe;//经过一些处理,将数据库异常抛给调用者处理}finally{try{con.close();}catch(SQLExceptione){e.printStackTrace();...}}运行程序后调用者得到如下信息java.lang.NullPointerExceptionatmyPackage.MyClass.method1(methodl.java:266)而不是我们期望的数据库异常。这是因为这里的con是null关系,在finally语句中抛出NullPointerException,在finally块中加入con是否为null的判断可以避免这种情况。遗漏异常请看如下代码:publicvoidmethod2(){try{...method1();//method1执行数据库操作}catch(SQLExceptione){...thrownewMyException("发生数据库异常:"+e.getMessage);}}publicvoidmethod3(){try{method2();}catch(MyExceptione){e.printStackTrace();...}}上面method2的代码中,try块捕获数据库异常SQLException后由method1抛出,它抛出新的自定义异常MyException。这段代码没有任何问题,但是看看控制台输出:MyException:Adatabaseexceptionhasoccurred:Theobjectname'MyTable'isinvalid。atMyClass.method2(MyClass.java:232)atMyClass.method3(MyClass.java:255)原来异常SQLException的信息丢失了,这里只能看到method2中定义的MyException的堆栈;而数据库在method1中发生的异常堆栈是看不到的。如何排除故障?唯一的办法就是在method1的代码行中逐行查找数据库操作语句。JDK的开发者也意识到了这种情况。在JDK1.4.1中,Throwable类增加了两个构造函数,publicThrowable(Throwablecause)和publicThrowable(Stringmessage,Throwablecause),构造函数中传入的原始异常堆栈信息会在printStackTrace方法中打印出来。但是对于还在使用JDK1的程序员来说。3、他们只能自己实现打印原始异常堆栈信息的功能。实现过程也很简单。只需要在自定义异常类中添加一个原始异常字段,在构造函数中传入原始异常,然后重载printStackTrace方法即可。首先调用类中保存的原始异常的printStackTrace方法,然后调用super.printStackTrace方法打印出原始异常信息。前面代码中出现的MyException类可以这样定义:importjava.io.PrintStream;importjava.io.PrintWriter;publicclassMyExceptionextendsException{privatestaticfinallongserialVersionUID=1L;//原始异常privateThrowablecause;//构造函数publicMyException(Throwablecause){this.cause=原因;}publicMyException(Strings,Throwablecause){super(s);this.cause=cause;}//重载printStackTrace方法打印出原始异常堆栈信息publicvoidprintStackTrace(){if(cause!=null){cause.printStackTrace();}super.printStackTrace();}publicvoidprintStackTrace(PrintStreams){if(cause!=null){cause.printStackTrace(s);}super.printStackTrace(s);}publicvoidprintStackTrace(PrintWriters){if(cause!=null){cause.printStackTrace(s);}super.printStackTrace(s);}}