Java基础第7章异常处理.docx
- 文档编号:5127032
- 上传时间:2022-12-13
- 格式:DOCX
- 页数:23
- 大小:27.06KB
Java基础第7章异常处理.docx
《Java基础第7章异常处理.docx》由会员分享,可在线阅读,更多相关《Java基础第7章异常处理.docx(23页珍藏版)》请在冰豆网上搜索。
Java基础第7章异常处理
第七章异常处理
为了构建健壮、灵活的代码,异常就是程序员必须要面对的问题。
异常处理也因此成为了衡量代码优劣的重要尺度。
当程序产生异常,能够启用相应的异常处理程序进行异常处理,使得程序能够继续运行下去。
本章学习目标:
了解异常的基本常识
熟悉异常处理的基本原则
掌握如何创建和使用自定义异常处理程序
7.1异常的基本概念
发现错误的最佳时机是在编译阶段,也能使系统的维护代价降到最低。
但是,编译期间不可能找出所有的错误,原因就在于:
对于JAVA语言来说,编译过程的本身除了把源程序编译成JAVA虚拟机能够执行的.class字解码文件外,仅仅对程序进行语法性验证。
剩下的问题就必须在运行期间去解决。
这就需要通过某种方式,把错误源的相关信息传递给某个错误处理者。
JAVA异常处理机制也因此而诞生了。
19781108gxy
420589106
7.1.1什么是异常
在程序运行时,打断正常程序流程的任何不正常的情况称为错误或异常。
“异常处理”的实现,最早可以追溯到20世纪60年代的操作系统。
经过几十年的积累,才使得“异常处理”被正式的纳入语言的范畴。
一个“注重实效的程序员”是不应该回避异常处理的。
但是很可惜,或许异常处理真的很困难吧。
大家都有意、无意的回避了其中的一些异常。
这是很不负责任的做法。
7.1.2JAVA标准异常
JAVA中的Throwable类是所有异常类的顶级父类,并且有两个直接子类。
一个是Error类,另一个是Exception类。
这两个子类代表JAVA异常的两种类型。
Error用来表示编译时和系统错误,Exception是与编程有关的所有类的父类,是可以被抛出异常的基本类型。
而JAVA程序员所关心也就是Exception类型的异常。
如果您看过SUN官方的JAVA文档会发现:
其实所有的异常类,除了类名不同外,其它的内容都极其的相似。
所以要想写出优雅的异常处理程序,关键是要理解JAVA异常概念以及如何在恰当的时间应用它。
7.1.3JAVA异常处理机制
JAVA程序的执行过程中如果出现异常,就会有几件事随之发生。
首先,自动生成一个异常类的对象,该对象包含了有关异常的基本信息,然后当前执行路径被终止,并且从当前环境中弹出异常对象的引用(其实这个异常对象的引用是被自动提交给JAVA运行时系统的),这个过程称为“抛出异常”。
当JAVA运行时系统接收到异常对象时,会寻找能处理这一异常的代码,并把当前异常对象交给其处理,这个过程称为“异常捕获”。
7.2捕获异常
首先让我们先看一个简单的示例。
例7.1:
publicclassfirstException
{
publicstaticvoidmain(Stringargs[])
{
inttemp1=20;
inttemp2=0;
System.out.println(temp1/temp2);
System.out.println("上面因为除数为零,所以应该对异常进行处理,否则本行将不能输出");
}
}
编译:
javacfirstException.java
运行:
javafirstException
这时程序就终止了,并抛出一个JAVA运行时捕获的异常,内容如下:
Exceptioninthread"main"java.lang.ArithmeticException:
/byzero
atfirstException.main(firstException.java:
7)
这段提示的意思是:
在main线程中产生了一个java.lang.ArithmeticException异常:
原因是除数为0,错误代码在firstException.java这个文件的第7行。
(线程的概念将在后面的章节进行讲解。
)。
下面我们对程序进行一下修改并保存,使之能够正常运行。
例7.2:
publicclassfirstException
{
publicstaticvoidmain(Stringargs[])
{
inttemp1=20;
inttemp2=0;
try
{
System.out.println(temp1/temp2);
}
catch(ArithmeticExceptione)
{
System.err.println("我是一个异常");
}
System.out.println("上面因为除数为零,所以应该对异常进行处理,否则本行将不能输出");
}
}
编译:
javacfirstException.java
运行:
javafirstException
结果:
我是一个异常
上面因为除数为零,所以应该对异常进行处理,否则本行将不能输出
虽然负数开平方绝对是不可以的,但是JAVA运行时系统却不会捕获该异常,只是把开平方的结果赋予NaN值。
这个NaN是“NotaNumber”,即非数值(也有称其为“不确定式”的,但是非数值可能更合适一些)。
但是比较有意思的是JAVA本身对NaN本身的定义,如下:
public static final double NaN;
因为对NaN的讨论已经超出本书的范围,所以在此不进行详细讨论,但是读者应该知道的是:
即便两个NaN值也是不相等的。
下面这个例子就能很好的说明这个问题:
例7.3:
publicclasstemp
{
doublea=-10;
a=Math.sqrt(a);
System.out.println(a);
System.out.println(a==a);
doubleb=Double.NaN;
System.out.println(b);
System.out.println(b==b);
}
编译:
javactemp.java
运行:
javatemp
结果:
NaN
False
NaN
False
从firstException.java这个例子不难看出,异常捕获是在try{}块中进行的。
try{}块中是可能产生异常的代码,这段代码在JAVA中称为监控区(guardedregion)。
而异常处理是在catch(){}中进行的。
对于异常处理程序来说,一个try{}块对应着一个或多个catch(){}块,和一个在语法上可有可无的finally{}块。
但是程序一旦包含了finally{}块,则不管是否发生异常,finally{}块里的语句是一定要执行的。
乍看上去有一点像现实中的“霸王条款”。
但正是这个貌似“霸王条款”的finally{}块,它对于引入了“垃圾回收”机制的JAVA来说,有着极其特殊的价值。
因为“垃圾回收”是由JVM控制的,我们不知道也不可能控制回收的时间。
对于像已经使用完或无效的数据库连接这样占用系统大量宝贵资源的“垃圾”来说,必须要在第一时间将其释放。
这时候JVM的“垃圾回收”机制的处理能力就显得有些苍白,在Web应用上更是如此。
这时如果程序中加上了一个finally{}块,并且在里面加上一行释放连接的代码,问题就很简单的解决了。
下面的这个小程序能有助于读者对异常处理的理解。
程序功能描述:
定义一个可以实现统计前N名学生的平均成绩的方法。
要求方法能接受学生成绩表和计算平均成绩的学生个数这两个参数。
其中成绩表为整型数组,学生人数为普通的整型变量,实现如下:
例7.4:
publicclassexampleException
{
publicstaticvoidmain(Stringargs[])
{
intgrades[]=newint[]{95,80,65,78,58};
//计算前10名学生的成绩。
countAverage(grades,10);
}
publicstaticdoublecountAverage(intgrades[],inttotal)
{
intsum=0;
doubleave=0.0;
booleanjudge=false;
try
{
for(inti=0;i { sum+=grades[i]; } ave=(double)sum/total; } catch(ArithmeticExceptione) { //其他的处理代码 System.err.println("total为零,不能计算"); judge=true; } catch(ArrayIndexOutOfBoundsExceptione) { //其他的处理代码 System.err.println("统计的学生个数超出成绩表中的学生总人数"); judge=true; } finally { //其他的处理代码 System.err.println("不管程序是否有异常,我都将会输出"); if(judge==true) { System.err.println("因为参数有误,将返回0.0"); return0.0; } else returnave; } } } 保存为exampleException.java 编译: javacexampleException.java 运行: javaexampleException 程序结果为: 统计的学生个数超出成绩表中的学生总人数 不管程序是否有异常,我都将会输出 因为参数有误,将返回0.0 读者可以试着把return语句放到try{}块中或者catch(){}块中,然后把finally{}块中的关于返回值的语句去掉。 看看程序能否通过编译并且正常运行。 7.3创建自定义异常 房地产开发商根据市场部调查来的结果,最终抽象出适合某一类型用户群的商品房模型。 经过施工使模型成为现房。 而有该类型需求的用户,在购买完商品房后,不可避免的要对其进行装修: 该掏壁橱的地方掏壁橱、该铺地毯的地方铺地毯……,最终设计出一套适合自己的住房。 这样做的原因很简单。 从开发商的角度来讲: 他只能根据用户群的需求,建一个比较符合用户要求的“筒子楼”。 而不可能为每一个用户量身定做住房(当然如果付得起费用的话,这也有可能)。 这其中最主要的原因是: 开发商在时间上耗不起。 从用户的角度来讲: 一屋子的水泥墙,连个床都没有。 所以装修也就避不可少了。 与现实中的房地产开发商比起来,SUN为JAVA异常这群“用户”定制的“JDK筒子楼”要高明得多: 里面有“床”、有“空调”、有“有电视”……。 但是不管怎样,它依然是“筒子楼”,所以就不可避免的满足不了我们所有的要求。 另外有些功能也不是我们所希望的那样。 基于这点,创建自定义异常就势在必行。 但是读者需要知道的是: 我们是在为“JDK筒子楼”装修,而非重建。 所以我们要“extendsException”。 另外,在为“JDK筒子楼”装修前,还是看看都提供了那些主要功能吧。 Java.lang软件包中的异常 Exception ClassNotFoundException ClassNotSupportedException IllegalAccessException InstantiationException InterruptedException NoSuchMethodException RuntimeException ArithmeticException ArrayStoreException IllegalArgumentException IllegalThreadStateException NumberFormatException IllegalMonitorStateException IndexOutOfBoundsException ArrayIndexOutOfBoundsException StringIndexOutOfBoundsException NegativeArraySizeException NullpointerException SecurityException 上面所列出的java.lang包中提供的这些异常基本能够满足初学者的使用需求。 其余包中的异常请参照相关资料。 如果书中的示例程序需要用到其它包中的异常,将给出特别的说明和解释。 另外读者需要知道的是: 异常的类库也在不断的扩大中,而且在第三方的异常库中依然可能存在其他的异常。 在使用的时候如果出现了奇怪的异常,则应该特别注意了。 Java的JDK中所提供的异常类数以百计。 程序员不可能也没有必要将它们全部记住,用的时候可以去查手册,里面有非常详细的信息。 而如果仅仅是要找到一个合适的异常类却有一个很简单也很巧妙的办法: 把可能产生该类异常的代码放到一个main()方法中,并且把代码设置成一定会抛出异常的状态。 这样我们就能在运行该段代码时,看到系统给出的提示。 下面给出一个小例子将有助于对上面叙述的理解: 例7.5: publicclassdemo { publicstaticvoidmain(Stringargs[]) { inttemp1=3; inttemp2=0; //除数为零,所以肯定会出现异常。 System.out.println(temp1/temp2); } } 保存为: demo.java 编译: javacdemo.java 运行: javademo 结果: Exceptioninthread"main"java.lang.ArithmeticException: /byzero atdemo.main(demo.java: 7) 这样我们就找到了除数为零是属于哪个异常了。 而这种方法对于不知道异常属于什么类型的情况则更为有用。 在创建用户自定义异常之前,有必要简单了解一下Exception类的原型。 Exception类有两个构造器: publicException(); publicException(Stringmsg); 下面将创建并使用一个自定义异常类。 这也是在exampleException类基础上进行改写的。 //创建自定义异常类: 例7.6: publicclassmyExceptionextendsException { publicmyException() {} publicmyException(Stringmessage) { super(message); } //重写父类的getMessage()方法 publicStringgetMessage() { returnsuper.getMessage()+"countAverage(intgrades[],inttotal)"; } } 例7.7: classfirstException { publicstaticdoublecountAverage(intgrades[],inttotal)throwsmyException { intsum=0; doubleave=0.0; booleanjudge=false; if(total<=0) thrownewmyException("人数不能是一个小于或等于0的数"); else if(total>grades.length) thrownewmyException("人数不能大于成绩表中的总人数"); else { for(inti=0;i { sum+=grades[i]; } return(double)sum/total; } } } 例7.8: classtest { publicstaticvoidmain(Stringargs[]) { intgrades[]=newint[]{95,80,65,78,58}; //计算前10名学生的成绩。 try { firstException.countAverage(grades,10); } catch(myExceptione) { System.out.println("\n下面将调用自定义异常重写的getMessage()方法,来输出异常的内容"); System.err.println(e.getMessage()); System.out.println("\n下面调用系统为父类定义的printStackTrace()来输出相关信息"); e.printStackTrace(); e.getLocalizedMessage(); } } } 保存为: myException.java 编译: javacmyException.java 运行: javatest 结果: 下面将调用自定义异常重写的getMessage()方法,来输出异常的内容 人数不能大于成绩表中的总人数countAverage(intgrades[],inttotal) 下面调用系统为父类定义的printStackTrace()来输出相关信息 myException: 人数不能大于成绩表中的总人数countAverage(intgrades[],inttotal) atfirstException.countAverage(myException.java: 29) attest.main(myException.java: 49) 从上面的例子不难看出,要创建自定义的异常类,其实很简单。 它与普通类的创建方法一样。 只不过自定义的异常类必须继承Exception或Exception的子类(虽然建议要继承的父类,越接近该类型的异常越好。 但是在实际编写代码的时候,却往往是继承Exception。 原因就是: 从Exception继承而来的自定义异常类,绝大多数情况都能满足程序员的需要。 而如果真的能找到这个接近该类型的异常类,往往就没有必要去编写自定义的异常类了)。 例7.9: 这样我们就得出了一个创建自定义异常类的模型: /*因为自定义异常往往是单独放在一个包中,为了能够被不同的包所引用,最好定义为public类型*/ publicclassuserExceptionNameextendsException { //构造器 publicuserExceptionName(){}; publicuserExceptionName(Sgringmsg) { super(msg); } //重写getMessage()方法 publicStringgetMessage() { //自定义的代码…… } //也可以自定义一些自己特殊的方法 } 这里要说明一下: 因为我们不能保证我们“装修好了的筒子楼”没有别人住,所这两个构造器最好全都定义出来。 当然还可以根据需要,再重载几个构造器以供使用。 另外getMessage()方法不一定要进行重载。 如果我们需要更详细的信息,可以调用Throwable类(它是Exception类的父类)的publicvoidprintStackTrace()方法。 其实publicvoidprintStackTrace()方法还有两个重栽版本,分别是: publicvoidprintStackTrace(PrintStream) publicvoidprintStackTrace(java.io.PrintWriter) 关于这个方法更详细的说明请参照其他资料。 在这里就不赘述了。 创建完自定义异常类后,接下来就是如何来使用它。 很显然“JDK筒子楼”不可能预料到我们会如此“装修”它,也就不可能预先抛出该类型的异常。 所以在使用自定义异常前,还应知道知道该如何抛出自定义异常。 [访问修饰限制符][static][返回类型]方法名([参数])throwsuseExceptionName { //自定义的代码 if(……) {throwuseExceptionName([可能的原因]);} } 其中throws为异常声明。 有点类似于C语言中的函数声明,目的就是告诉机器: 我可能会抛出这样的异常,而且不管我是否抛出这类异常你都应该对这(些)类型的异常进行检测。 当然,既然说是“可能会”,那么也就“可能不会”的情况发生,甚至真的在方法的实现中不抛出这类异常(但是调用该方法的时候必须对声明的抛出异常进行捕获。 否则将不能通过编译)。 这样做的好处有两个。 第一个是: 这样做能够使调用者知道使用该方法可能会面临什么样的问题,进而进行补救;第二个好处是: 如果我们定义的是抽象类或接口时声明了要抛出的异常,那么派生类或接口的实现就能够抛出这些预先声明的异常了(这些被声明的异常类必须是已经定义好的,否则程序是不能通过编译的)。 但是作为初学者需要知道的是: 我们不可能预见所有的异常,所以设计出了糟糕的异常声明也不要气馁。 因为“任何进步都是需要代价的”,只要有勇气去做、去尝试就够了。 或许读者会问“自定义异常真的这么简单吗”。 答案是肯定的。 而且JDK中所定义的异常类,有些甚至会更简单些。 这并不是那些JAVA开发者的知识不够渊博,而是因为设计异常类所追求的目标就是尽可能简练地返回错误信息,以帮助程序员更好地调试程序。 所以试图设计功能强大且十分华丽的自定义异常类的做法是非常不可取的。 那样做不但违背了提出异常概念的初衷,而且使代码变得更加晦涩难懂。 7.4异常的尴尬 或许这个标题有点难以理解。 这么优秀的概念也有尴尬吗? 回答是肯定的,而且异常的尴尬程度与程序员对异常功能的期盼是成正比的。 因为异常处理并不是万能的,它仅仅是保障程序正常运行的最后屏障。 要想构建健壮灵活的代码,异常的确是必不可少的。 但是最主要的确是在编码前期进行的“需求分析”、“需求定义”、“功能模块的划分”……,因为这些才是构建健壮代码的基石。 试想一下: 地基已经倾斜的“比萨城斜塔”,即便在当今科技的鼎力支持下进行的补救措施,依然不能阻止它继续倾斜。 那么,我们还敢期盼异常处理的补救工作真的能够“起死回生”吗? 作为语言的一个功能模块,异常依然有着自己缺点和不足。 下面将对此进行详细的说明,并给出相应的解决办法。 运行一些大型软件的时候,偶尔会出现文件丢失的现象。 而异常也同样面临着丢失的危险。 前者的发生频率是“sometimes”,而后者如果程序员不加注意的话却是“often”甚至是“always”。 异常丢失有些可能是语言本身的问题,但大多数是人为造成的。 看一看下面的例子: 例7.10: classlostExceptio
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 基础 异常 处理