什么是异常,我们为什么要关心它?
单词“exception”是短语“exceptional event(异常事件)”的缩写,它定义如下:
定义:异常是程序在执行时发生的事件,它会打断指令的正常流程。
许多种类的错误将触发异常,这些问题从像硬盘(crash)坠毁这样的严重硬件错误,到尝试访问越界数组元素这样的简单程序错误,像这样的错误如果在java函数中发生,函数将创建一个异常对象并把他抛出到运行时系统(runtime system)。异常对象包含异常的信息,包括异常的类型,异常发生时程序的状态。运行时系统则有责任找到一些代码处理这个错误。在java技术词典中,创建一个异常对象并把它抛给运行时系统叫做:抛出异常(throwing an exception)。
当某个函数抛出一个异常后,运行时系统跳入了这样一个动作,就是找到一些人(译者注:其实是代码)来处理这个异常。要找的处理异常的可能的人(代码)的集合(set)是:在发生异常的方法的调用堆栈(call stack)中的方法的集合(set)。运行时系统向后搜寻调用堆栈,从错误发生的函数,一直到找到一个包括合适的异常处理器(exception handler)的函数。一个异常处理器是否合适取决于抛出的异常是否和异常处理器处理的异常是同一种类型。因而异常向后寻找整个调用堆栈,直到一个合格的异常处理器被找到,调用函数处理这个异常。异常处理器的选择被叫做:捕获异常(catch the exception)。
如果运行时系统搜寻整个调用堆栈都没有找到合适的异常处理器,运行时系统将结束,随之java程序也将结束。
使用异常来管理错误,比传统的错误管理技术有如下优势:
1. 将错误处理代码于正常的代码分开。
2. 沿着调用堆栈向上传递错误。
3. 将错误分作,并区分错误类型。
1. 将错误处理代码于正常的代码分开。
在传统的程序种,错误侦测,报告,和处理,经常导致令人迷惑的意大利面条式(spaghetti)的代码。例如,假设你要写一个将这个文件读到内存种的函数,用伪代码描述,你的函数应该是这个样子的:
readFile{
open the file; //打开文件
determine its size; //取得文件的大小
allocate that much memory; //分配内存
read the file into memory; //读文件内容到内存中
close the file; //关闭文件
}
匆匆一看,这个版本是足够的简单,但是它忽略了所有潜在的问题:
· 文件不能打开将发生什么?
· 文件大小不能取得将发生什么?
· 没有足够的内存分配将发生什么?
· 读取失败将发生什么?
· 文件不能关闭将发生什么?
为了在read_file函数中回答这些错误,你不得不加大量的代码进行错误侦测,报告和处理,你的函数最后将看起来像这个样子:
errorCodeType readFile {
initialize errorCode = 0;
open the file;
if (theFileIsOpen) {
determine the length of the file;
if (gotTheFileLength) {
allocate that much memory;
if (gotEnoughMemory) {
read the file into memory;
if (readFailed) {
errorCode = -1;
}
} else {
errorCode = -2;
}
} else {
errorCode = -3;
}
close the file;
if (theFileDidntClose && errorCode == 0) {
errorCode = -4;
} else {
errorCode = errorCode and -4;
}
} else {
errorCode = -5;
}
return errorCode;
}
随着错误侦测的建立,你的最初的7行代码(粗体)已经迅速的膨胀到了29行-几乎400%的膨胀率。更糟糕的是有这样的错误侦测,报告和错误返回值,使得最初有意义的7行代码淹没在混乱之中,代码的逻辑流程也被淹没。很难回答代码是否做的正确的事情:如果函数分配内容失败,文件真的将被关闭吗?更难确定当你在三个月后再次修改代码,它是否还能够正确的执行。许多程序员“解决”这个问题的方法是简单的忽略它,那样错误将以死机来报告自己。
对于错误管理,Java提供一种优雅的解决方案:异常。异常可以使你代码中的主流程和处理异常情况的代码分开。如果你用异常代替传统的错误管理技术,readFile函数将像这个样子:
readFile {
try {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
} catch (fileOpenFailed) {
doSomething;
} catch (sizeDeterminationFailed) {
doSomething;
} catch (memoryAllocationFailed) {
doSomething;
} catch (readFailed) {
doSomething;
} catch (fileCloseFailed) {
doSomething;
}
}
注意:异常并不能节省你侦测,报告和处理错误的努力。异常提供给你的是:当一些不正常的事情发生时,将所有蹩脚(grungy)的细节,从你的程序主逻辑流程中分开。
另外,异常错误管理的膨胀系数大概是250%,比传统的错误处理技术的400%少的多。
2. 沿着调用堆栈向上传递错误。
异常的第二个优势是,可以沿着函数的调用堆栈向上报告错误。设想,readFile函数是一系列嵌套调用函数中的第四个函数:method1调用method2, method2 调用method3,最后method3调用readFile。
method1 {
call method2;
}
method2 {
call method3;
}
method3 {
call readFile;
}
如果只有method1对发生在readFile中的错误感兴趣。传统的错误通知技术迫使mothed2和mothed3沿着调用堆栈向上传递readFile的错误代码,直到到达对错误感兴趣的mothed1。
method1 {
errorCodeType error;
error = call method2;
if (error)
doErrorProcessing;
else
proceed;
}
errorCodeType method2 {
errorCodeType error;
error = call method3;
if (error)
return error;
else
proceed;
}
errorCodeType method3 {
errorCodeType error;
error = call readFile;
if (error)
return error;
else
proceed;
}
就像前面所了解到的,java运行时系统向后(baskward,也就是向上)搜寻调用堆栈,找到对处理这个异常感兴趣的函数。Java函数可以“躲开(duck)”任何在函数中抛出的异常,因此,允许函数穿越过调用堆栈捕获它。那个唯一对错误感兴趣的函数method1,将负责(worry about)侦测错误。
method1 {
try {
call method2;
} catch (exception) {
doErrorProcessing;
}
}
method2 throws exception {
call method3;
}
method3 throws exception {
call readFile;
}
然而,就像你在伪代码中看到的,在“中间人(中间的函数methoed2和method3)”忽略异常需要受到影响,一个函数如果要抛出一个异常,必须在函数的公共接口声明中使用throws关键字指定。因此,一个函数可以通知它的调用者自己会抛出什么样的异常,这样调用者就可以有意识的决定对这些异常做些什么。
再一次注意异常和传统错误处理方式,在膨胀系数和迷惑系数的区别。使用异常的代码更简洁,更容易理解。
3. 将错误分作,并区分错误类型。
异常(们)经常被划分成类别或组。例如,你可以想象一组异常,它们中每一个都表示关于数组操作的的特殊的异常:索引超出数组的范围,要插入的元素是错误的类型,要查找的元素不在数组中。而且,你能想象一些函数将处理所有这类的异常(关于数组的异常),其它一些函数将处理特殊异常(仅仅是无效索引异常)。
由于在java程序中所有的异常首先是一个对象,异常的分组和分类成为类继承自然而然的结果。Java异常必须是Throwable或者是Throwable子类的实例。就像你可以从其它java类继承一样,你也可以创建Thowable的子类,或者孙子类(从Thowable子类继承)。每个叶子节点(没有子类的类),代表一种特殊类型的异常,每个节点(node)(有一个或者更多子类的类)代表一组有关联的异常。
例如,在下列图表中,ArrayException是Exception的子类(Throwable的一个子类),它有三个子类。
InvalidIndexException, ElementTypeException,和NoSuchElementException都是叶子类,它们都是在操作数组时发生的错误。捕获异常的一种方法是仅仅捕捉那些叶子类的实例。例如,一个异常控制器仅仅捕捉无效索引异常,它的代码像这个样子:
catcatch (InvalidIndexException e) { . . .}ArrayExecption是节点(node)类,代表你在操作数组时发生的任何错误,包括特定代表一个错误的所有子类中的任何一个。如果一个函数要一组或者一类异常,只要在在cathc语句中指定这些异常的超类(superclass)。例如,要捕捉所有数组异常而不指定具体类型,异常控制器将捕捉ArrayException:
catch (ArrayException e) { . . .}这个异常控制器将捕捉所有数组异常,包括InvalidIndexException, ElementTypeException, 和 NoSuchElementException。你可以在异常控制器的参数e中找到异常的精确的类型。你甚至可以建立这样的异常控制器,它处理所有的Exception。
catch (Exception e) { . . .}上面出示的异常控制器实在时太通用了,这样做使你的代码处理太多的错误,需要处理许多你不希望处理的异常,因而不能正确的处理异常。通常我们不推荐写通用的异常处理器。
就像你看到的,你能创建一组异常,并处理这些异常以通用的方式;你也能指定异常类型去区分异常,并处理异常以精确的方式。
What’s Next?
现在你能懂得在java程序中使用异常的好处,现在到了学习怎样去用它们的时候了。
转载自:the java tutorial
发表评论
-
单一入口应用程序概述
2011-12-02 16:00 1828什么是单一入口应用程 ... -
PHP常用函数
2011-12-01 11:50 1485数组函数 array_chunk ... -
JAVA堆栈
2011-11-17 13:23 1252Java栈与堆 ----对这两 ... -
Struts2拦截器的使用
2010-06-25 16:27 1280如何使用struts2拦截器,或者自定义拦截器。特别注意,在使 ... -
第7章 使用filter过滤请求
2010-05-31 10:39 1696第 7 章 使用filter过滤请求 注意 Filter虽然很 ... -
初试Filter对权限和session的控制
2010-05-28 15:11 1196初试Filter对权限和session的控制 用Filter ... -
Model 1和Model 2
2010-05-19 13:22 1760Model 1和Model 2 对于Java阵 ... -
对java中的线程感想
2010-05-04 12:13 1007对java中的线程感想 1.进程和线程的区别 通俗一 ... -
javabean标签库的解释说明
2010-03-26 16:29 1225javabean标签库的解释说明 在JavaServer Pa ... -
一个体现Java接口及工厂模式优点的例子
2010-03-26 11:06 3419这篇文章我也不知道再哪看到的了,感觉写得还不错,转载了.... ... -
JDBC访问所有数据库的完整步骤
2010-03-25 09:31 1492JDBC访问所有数据库的完整步骤 1 将数据库的JDBC驱动加 ... -
Java中throws和throw的区别
2010-03-24 15:10 3111java处理异常方式 ... -
JSP常用内置对象使用说明
2010-03-22 09:32 1331内置对象特点: 1. 由JSP规范提供, ...
相关推荐
异常处理机制通常由编译器和异常处理机制的运行时支持函数共同实现,因此,如何正确高效地实现异常处理机制是设计编译器和异常处理运行时支持函数所要关心的重要问题。 Java程序的编译运行有两种方式:在JVM上动态编译...
首步我们一般在程序中先处理我们完成异常处理的类,完成异常处理的功能,因为可能除数为0是会出现异常,我们查找java.lang包中各个Exception类,发现RuntimeException类集合中的ArithmeticException可以处理运算异常...
这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用map.remove(key)方法时,虽然remove方法有返回值,但是我们通常都不会...
正在下载25Karma 要在本地启动项目,您需要在计算机上安装 完成后,请按照下列步骤操作: 从GitHub的分支下载存储库解压缩.zip 在根目录(找到package.json文件的位置)中运行npm install 在根目录中运行npm start...
作为程序员,平时最担心见到的事情就是程序发生了崩溃,无论是指针越界还是非法操作,都将给我们的应用系统造成巨大的损失。...我们更为关心的是程序中的哪一行导致了系统崩溃,这样我们才能有针对性的进行改正。
最后 我们要说的是 无论谁写了一本书 他所省略掉的 往往与他所讲述的内容一样 VII 译序 重要 C++语言的某些方面 比如构造函数的工作细节 在什么条件下编译器会创建内部临 时对象 或者对于效率的一般性考虑 虽然...
然而当前人们主要关心TPM的实现以及其上的应用开发,却很少讨论TPM本身的安全性。这样一方面很难使人们相信TPM本身是安全的,另一方面也不能很好的将TPM应用到安全领域中。对用户和TPM交互时所遵循的重要协议——...
但我想Delphi程序员也应该对该问题有所了解,知道语言为我们提供了什么而使得我们如此轻松,不必理会它。正所谓“身在福中须知福”。 我们知道,类的构造函数是没有返回值的,如果构造函数构造对象失败,不可能依靠...
常常这些满足条件的记录如此之多,一方面在同一个页面显示显得异常臃肿而不切实际,另一方面用户通常也不会对他们都感兴趣,他们似乎更关心按一定规则排序出现在某些开始位置的若干记录。这就要求我们对满足条件的...
fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个...
需要程序员经常刷题吗他妈的 ...因为你是一个了不起的程序员,不需要被错误和异常分心。...如果你真的不关心任何错误,只需使用他妈的错误和异常处理程序。 set_error_handler(['\FuckIt\FuckIt', 'errorHandler
3.15 我要检查一个数是不是在另外两个数之间,为什么if(abc)不行? 3.16 为什么如下的代码不对?inta=1000,b=1000;longintc=a*b; 3.17 为什么下面的代码总是给出0?doubledegC,degF;degC=5.0/9*(degF-32); ...
3.15 我要检查一个数是不是在另外两个数之间,为什么if(a b c)不行? 40 3.16 为什么如下的代码不对?int a=1000, b=1000; long int c=a * b; 40 3.17 为什么下面的代码总是给出0?double degC, degF; degC= ...
如何巧妙地减少甚至避免干扰始终是设计者们关心的重点,其中单片机的抗干扰设计就是较为重要的一环,本文将为大家介绍与上拉电阻有关的单片机抗干扰。 单片机的输入阻抗解析 想要实现单片机刚干扰,首先要综合考虑...
看完了前面几段,我的朋友提出了不同的意见:C#不是Java的Clone,它只是长得有些像Java而已,其实面向对象、中间语言什么的也不是什么新玩意儿,非Sun独创,有文为证:华山论剑:C#对Java。另外他对我上一集中说...
当一个 节点不可用或者不能处理客户的请求时,该请求会及时转到另外 的可用节点来处理,而这些对于客户端是透明的,客户不必关心 要使用资源的具体位置,集群系统会自动完成。 HA 集群系统硬件拓扑形式 基于共享磁盘...
1.为什么要封装? 封装就是要把数据属性和方法的具体实现细节隐藏起来,只提供一个接口。封装可以不用关心对象是如何构建的,其实在面向对象中,封装其实是最考验水平的 2.封装包括数据的封装和函数的封装,数据的...
ajaxvalidator是大家问的最多的问题,修正一个bug(感谢网友“じ龍峸√”),并把大家最关心的问题,再做一次阐述。 bug现象:无论校验有没有校验通过,当控件再次得到焦点而再次失去焦点的时候tip里的提示就会停滞...