第八章_异常处理

  • 格式:doc
  • 大小:160.00 KB
  • 文档页数:18

8 异常处理(Exception)逝去的2010年,“我爸是李刚”这句经典调侃火的冒烟。

我们可爱的网友把“我爸是李刚”这句话用到了所有能用的地方。

之所以火起来还要从2010年10月16日晚在河北大学校内发生的一起车祸说起。

肇事者李启铭酒后驾驶朋友的迈腾轿车在河北大学新校区内,将两名女生撞倒。

被保安拦下后竟高呼“我爸是李刚”。

警方经对李启铭采血检测,鉴定为醉酒驾驶。

伤者之一陈晓凤因抢救无效死亡。

哦,别朝我扔西红柿,我知道要讲的是异常。

这只是一个引子,想想车祸与异常有什么内在联系。

车辆按照交通规则在路上行驶,可以顺利到达目的地。

同样一套程序按照预先设定的正确流程运行,可以顺利得到正确结果。

但是你要知道,永远都有人怀着侥幸心理。

也许他们一手拿着二锅头一手握着方向盘在想,瞧我多酷!车祸被他们麻痹的神经触发。

接下来,警察封堵这条道路,其他车辆只能绕行,或者干脆等在那里不耐烦地按着车笛。

我们可以俏皮的说公路抛出(throw)了一个异常,不能继续正常运行下去了。

而交通警察的责任就是在发生交通异常时抓住(catch)这个异常并且解决掉它。

以上描述还不能给你拨云见日的感觉,异常仍然是一位朦胧的闺中淑女。

让我们通过后面的学习来圈起闺房的珠帘吧。

8.1 Java异常处理机制(Exception handling mechanism)截止到目前,本书教授读者的代码都是正确的。

其实我们把许多BUG藏起来了。

试想当一个控制台程序(例8.1.1)要求输入两个数字以计算它们的和。

我们通常会乖乖的输入两个正确的数字然后看见正确的结果。

但是有人将一个0(零)输入成了O(英文字母)妄想骗过计算机的眼睛,或者手指不小心按错了键,控制台将会输出一些红色的信息提醒我们“我不能再继续下去了”,世界变得不在和谐。

例:8.1.1控制台打印信息如下:这是NumberFormatException(数字格式化异常)异常(稍后讲解异常类型),通过提示我们了解到,当把“1O”字符串转换为int型值时发生了错误。

下面详细列出了异常出现的位置精确到代码行,以及调用抛出异常方法的上层方法。

这张列表将会根据调用关系逐层汇报(抛出)到我们写的main方法。

通过例8.1.1的启发读者是否也能说出一些可能抛出异常的情况。

现实中可能引起异常的情况有很多,以下列举一些常见的情况,欢迎您补充:●意外的用户输入(如例8.1.1);●找不到要打开的文件;●打印时打印机缺纸;●数据库连接意外关闭;●网线被一只饥饿的老鼠咬断了;●我们无限崇拜的JVM不能正常工作了;●程序员们移花接木得出的豆腐渣代码有个潜在BUG;以上列举的情况有些我们可以避免,有些不可避免。

也就是说我们无法彻底消除异常。

我们该考虑的是当异常发生时我们如何去处理。

在传统的程序异常处理领域并没有一个统一的定论。

JA V A的大神们借鉴了C++的异常处理机制,并在此基础上做了一些试验性改变,形成了JA V A自有的异常处理机制。

JA V A程序运行在JVM(JA V A虚拟机)中,所以当程序发生异常后JVM会首先知道这一情况,并立即通过JA V A的异常处理机制将出现的问题封装成包含了错误信息的异常对象(Exception Object)抛出(throw)给调用出错方法的上层方法,看它是否能够抓住(catch)这个异常并解决掉它。

如果它也无能为力那就继续向上抛出,这个动作会一直抛出直至最上层的调用方法。

如果它也没有办法解决这个问题,那么程序就只能歇菜了。

如果问题在某一层得到解决,程序便可以从这一层开始继续运行下去。

看看例8.1.2这段无聊的代码,运行后控制台打印的信息,是不是如我料想的那样层层抛出。

例8.1.2分析代码,我们精心设计了一个除零的操作并产生了ArithmeticException(错误的运算条件异常)异常。

从打印的信息认证了层层抛出的伟大猜想。

这对开发人员来讲已经足够的明白,但却不是用户想要的形式。

用户会直接打电话来对你吼“我们付了那么多钱难倒就给我们看这些难看的错误信息吗?”。

是时候拿出我们的异常处理武器来武装脆弱的代码对抗异常了。

首先让我们给用户一个有好的提示,试着将例8.1.2中的method3方法体改为如下代码并运行程序查看控制台信息。

控制台打印信息如下:分析代码是怎么做到这一切的。

(1)首先我们把可能会抛出异常的语句放进try{ …your code…}结构的语句块中。

(2)try(尝试)是JA V A关键词,从字面意思理解是尝试运行包含的代码块。

尝试即是不确定的意思,有成功的可能,也有失败的可能。

(3)显然这里出现了分支,在try语句块后跟着的catch( Throwable e){…your code…}结构的语句块就是失败时的一个支路。

只有catch(抓住)括号中声明的异常类型(关于异常类型在后面详细研究,此处只要大约有个印象就好)以及继承自此异常的子孙异常类型才会触发catch语句块执行。

在本例中我们声明了一个catch语句块来处理ArithmeticException异常。

当发生ArithmetcEixception异常时,程序会自动进入catch语句块支路执行。

一个try后面可以跟随多个catch语句,每个catch处理一种异常类型。

有一点需要大家注意,当catch(抓住)的两个异常有父子关系时,必须把子类异常放在父类异常后面,否则我们的子异常处理语句块将永远都不会被执行到。

这就像我们上学时按小大个排队一样,要有秩序。

再来尝试一个有多个catch语句块的代码是什么样子的。

试着将例8.1.3中的method3方法体改为如下代码并运行程序查看控制台信息。

例8.1.4控制台打印信息如下:分析例8.1.4代码。

try语句块中首先抛出NumberFormatException异常,JVM 发现这个异常后在try后面寻找能够处理此异常的catch语句块执行。

于是控制台打印出一条较为有好的提示信息。

当语句中可能抛出更多种类异常时,理论上你可以无限加长你的catch清单,但我们不建议这么做。

关于提示其实还有更棒的主意。

用过WINDOWS操作系统的你一定见过,某个程序出现非法操作或错误提示是每个使用电脑的人都会遇到的情况,而此时,WINDOWS 会启动错误报告机制,询问用户是否发送报告给微软公司了解错误。

不过现在我们没有必要把事情做的面面俱到,毕竟我们只是想学习异常。

还记得switch/case吗?它与try/catch是同一种思想在做两类事情时的不同形式。

两种语法都是多分支结构。

那你是否还记得switch/case语法后面可以附加一条default语句。

只要我们不使用break或者return跳出,default就一定会被执行。

既然是一种思想的两种形式,try/catch也吸收了default的语法,在这里我们叫它finally。

finally比default更加尽职尽责,就像生病了也要坚持上班的海绵宝宝。

无论try语句块是否抛出异常;catch是否抓住了异常;在某个catch中又把异常重新抛出(请参见后续内容);或者是在catch中写了一条return语句;finally都会被执行。

也就是说只要try/catch最后包括finally语句块,finally 内的代码就一定会被执行。

使用finally最经典的用途是释放try语句块内被占用的资源(如数据库连接、被打开的文件流等)。

这可以确保资源被释放掉。

键入例8.1.5代码看看finally的力量。

例8.1.5控制台打印信息如下:分析控制台打印结果,四个方法中的finally语句块都被执行了。

这证明以后我们可以放心的把收尾工作留给finally,它会处理的很好。

再来看看下面的例8.1.6,我们将从中学习到另外一个异常处理关键字throws (抛出),这个关键字是JA V A用来约束开发者强制做异常处理的机制,为什么需要强制做异常处理?这是JA V A设计者的一种考虑。

JA V A的设计者认为在软件运行过程中,其实有很多意外情况可能发生,而且这些意外很常见。

比如程序需要打开一个文件,但是文件已经被移走了。

或者程序要向一个文件中写入一些内容,但是硬盘里面已经被塞的满满。

这些情况都是可以预见的问题。

JA V A强制开发者针对这些可以预见到的,非程序设计问题导致的运行时异常必须做处理。

如果不处理,程序将无法通过编译。

这类异常有一个官方的称呼叫做已检查异常(稍后介绍)。

显然与之对应的另一种异常就是未检查异常(稍后介绍)。

前面接触到的ArithmeticException与NumberFormatException异常都是未检查异常。

我们可以试着将例8.1.4中的try{..}catch(){…}语句注释掉,且将变量的值改为正确的,程序可以顺利运行完。

下面将8.1.6的代码录入到你的eclipse中看看会发生什么情况!细心的读者录入代码后可能已经发现在代码char c = (char) System.in.read();一行前面有一个红色的小叉,代表这一行有错误,错误不被解决程序就无法被编译器编译。

点击这个图标,Eclipse给出两个解决错误提示选项,“Add throws Leclaration”(增加抛出声明)与“Surround with try/catch”。

后一种解决方式我们已经学习过,试着选择第一种方式看看编辑器为我们生成的代码。

在方法名后多了一句“throws IOException”。

也就是声明此方法可能抛出IOException异常(IO 异常)。

IOEception是已检查异常,所以编译器强制我们去处理它。

一个方法可以声明抛出多种异常,语法是throws后面依次列出所有可能抛出的异常,每种异常之间以“,”逗号分割。

读者可以自己尝试写一个抛出多个异常的方法,别怕你一定能做到。

将发生的异常抛出而不是自己去处理它,并不代表我们懒惰。

清晨你好心帮助邻居把庭院里的雪扫的干干净净,你觉得会得到尊重与感激。

可是,你的邻居昨晚计划明天与家人堆一个雪人,为此兴奋了一夜。

而你却打乱了他的计划。

面向对象的世界被我们描述为编程世界的天堂,因为重用的特性帮助我们可以写更少的代码实现更多的功能。

你刚刚完成了一个很棒的方法,几位同事发现你的方法可以很好的帮助他们解决手头儿的麻烦,他们直接调用你的方法来完成工作。

这里有一点小麻烦,你的方法在对文件进行操作时可能抛出IOException 异常,当然,你已经在你的方法里面try/catch了这个异常,并且打印出一句很友好的提示“嘿,老兄,我没有找到你说的那个文件,你是不是搞错了路径或者名字?”。

这看起来很和谐,但你的同事们不满足,他们也许正在为一些不同语言的客户开发软件,也许有人需要使用英语来提示用户,也许有人需要用火星文。