标题标题标题,第一级,第二级,第三级,*,标题标题标题,第一级,第二级,第三级,第四章,异常,/,错误处理中的安全,异常,/,错误处理是程序设计中的常见内容,异常,/,错误处理的技巧和程序的安全性有着密切的关系。科学的异常,/,错误处理方法,是系统安全性的重要保障。,一般说来,程序开发过程中可能出现的问题有如下几种:,编译错误:程序语法写错了,比如在,C+,中,,int a,写成了,Int a,,这种错误,编译器能够进行提示,一般比较容易解决。,运行错误:程序语法没有问题,但是在运行的时候发生了问题。比如连接数据库代码本来是正确的,但是运行的时候数据库突然断电,程序不能正常运行,这是在代码编写阶段应该预计到的,可以由异常处理解决,(Java,语言中定义了,Error,和,Exception,,都是为了解决此类问题,),;在某些语言,(,如,VB),中,没有面向对象的异常处理机制,此时设计了面向过程的错误处理方法来解决这个问题。,另外一种是逻辑错误,程序语法没有问题,也没有异常,但是就是得不到正确的结果,这需要靠程序员非常高超的编程经验来进行处理;这不属于本章研究的范围。,4.1,异常,/,错误的基本机制,4.1.1,异常的出现,如前所述,,异常主要是针对程序语法没有问题时,在运行的过程中出现的突发情况,。以,Java,语言为例,如下代码的主要作用是让用户输入一个数字,显示其平方,代码如,P04_01.java,。,运行这个程序,按照正常输入“,12”,,能够打印正确结果。,但是,用户的输入是不可预计的。如果用户不小心输入一个无法转换成数值的字符串,如“,12o”,,结果如下:,界面上没有出现结果,而是打印了一堆莫名其妙的东西。如果这个程序给用户使用,用户会觉得莫名其妙,也就是说这里,没有给用户一个较为友好的界面,,至少应该提示用户格式输错了;更进一步说,这种问题如果事先不能预见并且认真处理,,严重的情况下甚至会造成系统运行的不正常,。,从以上的程序可以看出,异常的出现,是在程序编译通过的情况下,,程序运行过程中出现一些突发情况,造成的,这些突发情况,需要有良好的预见性,预先进行处理,以保证系统的安全性;这就对程序员提出了更高的要求。实际上,要预见程序可能出现的所有异常,几乎是不可能的。,常见异常可能出现的场合如:,访问数据库时,数据库停止工作;,访问文件,文件恰好在被另一个程序访问;,输入一个以,0,当除数的数值;,类型转换,对象未分配内存;等等。,从上面可能出现异常的场合可以看出,异常是几乎所有高级语言都可能出现的情况,在面向对象的语言里面,,C+,、,C#,等也会出现类似的情况,包括一些非面向对象的语言,如,VB,,也必须要面对程序运行过程中的异常现象。虽然处理方法不同,但本质类似。,提示,值得一提的是,异常和错误实际上在不同的语言中,有不同的说法。一般说来,异常叫做,Exception,,错误叫做,Error,。,Java,中定义了,Exception,和,Error,,来处理异常和错误,本章主要是针对,Exception,进行讲解;,VB,中主要处理的对象是,Error,,实际上和,Java,中的,Exception,更加类似,只是说法不同。,4.1.2,异常的基本特点,从上节的程序可以看出,从控制台的打印来看,程序在底层有一个提示:,意思是说出现了一个异常,并且显示了异常出现的位置在第,11,行:,无法将字符串转换为数值。,该处,异常类型为:。可以查看文档,找到该类,在文档中非常详细地说明了该异常出现的原因:,翻译成中文是:当试图将一个不符合数值格式的字符串转成数值时,程序抛出该类异常。,double number=Double.parseDouble(str);,提示:,不管什么语言的初学者,一看到程序抛出异常就非常畏惧,这很正常。不过,如果在测试的过程中,程序出现异常信息,有时候可以成为排错的良好手段。一般情况下,此时可以首先查看异常种类,根据文档查询该种异常出现的原因;然后查看异常消息和异常出现的地点,可以顺利地解决编程中出现的问题。,当系统底层出现异常,实际上是将异常用一个对象包装起来,传给调用方,(,客户端,),,俗称抛出,(throw),。比如在这个程序里面,发生了数字格式异常,这个异常在底层就被包装成为的对象抛出。异常对象抛出给谁呢?抛出给函数的调用者;如果调用者具有对异常处理的代码,则将异常进行处理;否则将异常继续向前抛出;如果直到用户端还没有对异常进行处理,异常将会在标准输出,(,如控制台,),上打印。,对于非面向对象语言,异常出现的原理类似。,程序中可能出现的异常有很多种类,如:,算术异常,如除数为,0,;,数组越界异常;,类型转换异常;,未分配内存异常;,数字格式异常;等等。,代码中出现异常,在该作用域内,出现异常代码后面的其他代码将不会执行。如上节代码中,在第,11,行出现了异常,那么第,11,行后面的代码将不会执行,当然也没有打印“程序运行完毕”。,由此可见,在复杂的系统中,异常处理不当,不仅仅是没有给用户一个友好界面的问题;更重要的是,如果对异常不闻不问,或者不恰当地处理异常,会给系统,带来巨大的安全隐患,。,4.2,异常捕获中的安全,4.2.1,异常的捕获,异常出现之后,我们可以通过查看文档来了解其发生的原因。但是,了解异常出现的原因,并不是最终目的,为了保证系统的正常和安全运行,将异常进行有效的处理,才是我们所需要的。,比如在,4-1,节中的案例,异常出现时,怎样进行处理才能让界面更加友好,系统更加安全?,要想进行异常处理,首先必须将异常进行捕获,(catch),,在面向对象的语言中,可以有两种方法进行异常的捕获:,就地捕捉异常;,将异常向前端,(,调用方,),抛出。,当一个模块中可能出现异常时,一般情况下,可以就地捕捉异常,过程如下:,1,:用,try,块将可能出现异常的代码包起来;,2,:用,catch,块来捕获异常并处理异常;,3,:如果有一些工作是不管异常是否出现都要执行的,则将相应的代码用,finally,块将其包起来。,提示:,对于,try-catch-finally,结构,有如下规定:,一个,try,后面必须至少接一个,catch,块;,try,后面可以不接,finally,块;,try,后面最多只能有一个,finally,块。,此时,代码的运行机制变为:当程序中出现异常时,,try,块后剩余的的内容不执行,转而执行,catch,块;不管是否出现异常,,catch,块是否执行,最后执行,finally,块。其机理如下:,try,代码,1,代码,2,出现异常,后面的代码,3,将不被运行,运行代码,4,代码,3,catch,(Exception1 ex1),代码,4.,运行之后,运行代码,5,,如果没有代码,5,,则运行代码,6,finally,代码,5.,运行之后,运行代码,6,代码,6,因此,上节中,访问文件的例子也就可以修改为:,try,1:,打开文件连接,2:,读文件,3:,将文件中的字符串转为数值,catch,(Exception1 ex1),/*,处理异常*,/,finally,4:,关闭文件,如果在第,3,步出现异常,由于关闭文件的工作写在,finally,块内,则该文件的关闭还是会被执行,保证了程序的安全性。,4.2.2,异常捕获中的安全,如前所述,一个,try,后面必须至少接一个,catch,,可以不接,finally,,但是最多只能有一个,finally,。我们知道,代码中可能出现的异常可能会有很多种类。如,Java,中常见的就有:未分配内存异常、未找到文件异常、数据库异常、格式转换异常、类型转换异常,等等。由于我们无法将所有的异常进行预见,怎样尽可能地捕获程序中可能出现的异常呢?,由于,try,块后面可以接多个,catch,块,因此,可以用某一个,catch,用于捕获某种异常。当,try,中出现异常,程序将在,catch,中寻找是否有相应的异常类型的处理代码,如果有,就处理,如果没有,继续向下找。所以如果要想让代码处理所有可能预见的异常,可以用如下方法:,try,/,可能出现异常的代码,catch,(,可预见的,Exception1 ex1),/*,处理,1*/,catch,(,可预见的,Exception2 ex2),/*,处理,2*/,.,finally,/,可选,此时,该代码的机制变为如下:,当,try,块内的代码如果出现异常,程序则在,catch,块内寻找匹配的异常,catch,块,进行处理;然后运行,finally,块。,以前面打开文件的代码案例为例,也就可以修改为:,try,1:,打开文件连接,2:,读文件,3:,将文件中的字符串转为数值,catch,(,文件型异常,ex1),/*,处理文件型异常*,/,catch,(,字符串转换型异常,ex2),/*,处理字符串转换型异常*,/,finally,4:,关闭文件,但是,以上代码还不能说是绝对安全的,由于系统的复杂性,此时我们能够预见的异常有文件型异常和字符串转换的异常,但是还可能有无法预见的异常,由于异常种类繁多,很多种类的异常排列在,try,块下方,导致程序规模过大,怎样用比较简便的方法,将异常“一网打尽”呢?在异常处理机制中,你可以加入一个,catch,块来处理其他不可预见的异常,代码变为:,try,1:,打开文件连接,2:,读文件,3:,将文件中的字符串转为数值,catch,(,文件型异常,ex1),/*,处理文件型异常*,/,catch,(,字符串转换型异常,ex2),/*,处理字符串转换型异常*,/,catch,(Exception ex),/*,处理其他不可预见的异常*,/,finally,4:,关闭文件,提示,应该指出的是:,catch(Exception ex),必须写在,catch,块的最后一个,以保证只有前面无法处理的异常,才被这个块处理。,于是,上节中的案例,可以改造成代码,P04_02.java,。,结果界面友好,并能够在,catch,块中处理异常。,关于以上代码,有两点需要注意:,1,:将大量代码放入,try,块,虽然可以保证安全性,但是系统开销较大,程序员务必在系统开销和安全性之间找到一个平衡。,2,:以上代码的,catch,块中,是简单的打印提示信息,实际的系统中,你可能要根据实际需求来使用不同的异常处理方法。,4.3,异常处理中的安全,4.3.1 finally,的使用安全,在异常处理过程中,,finally,块是可选的,实际上,,finally,是为了更大程度上保证程序的安全性。看如下代码:,public void,fun(),try,/,连接文件,/,读取文件,/,关闭文件,catch,(Exception ex),/,处理异常,函数,fun,中,,try,内进行连接文件和读取文件的工作,,catch,内处理异常,根据前面的介绍,该代码不安全。如果程序在连接文件之后,由于某些不可预见的原因,出现异常,程序将会在,catch,块中直接处理异常,但是文件没有关闭,给文件访问带来隐患,怎么办?难道在,catch,内增加关闭文件的代码吗?这样关闭文件就写了两次了。在这里可以用,finally,来实现。,finally,块中的代码,不管前面是否发生异常,代码都会执行。所以这段代码是安全的。,不过,这其中隐含着另一个问题:,finally,的出现似乎是可有可无的!,public void,fun(),try,/,连接文件,/,读取文件,/,关闭文件,catch,(Exception ex),/,处理异常,不管是否出现异常,在该程序结构中,关闭文件的工作也会进行。那么,代码放在,finally,块内,是否和不放在,finally,快内效果一样呢?也就是说,,finally,是否可以省略呢?,以,Java,为例,修改本章案例的代码为,P04_03.java,。,如果用户不小心输入一个无法转换成数值的字符串,如“,12o”,,结果如下:,说明,finally