什么是异常,我们为什么要关心它?
单词“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 1861什么是单一入口应用程 ... -
PHP常用函数
2011-12-01 11:50 1533数组函数 array_chunk ... -
JAVA堆栈
2011-11-17 13:23 1319Java栈与堆 ----对这两 ... -
Struts2拦截器的使用
2010-06-25 16:27 1320如何使用struts2拦截器,或者自定义拦截器。特别注意,在使 ... -
第7章 使用filter过滤请求
2010-05-31 10:39 1738第 7 章 使用filter过滤请求 注意 Filter虽然很 ... -
初试Filter对权限和session的控制
2010-05-28 15:11 1246初试Filter对权限和session的控制 用Filter ... -
Model 1和Model 2
2010-05-19 13:22 1792Model 1和Model 2 对于Java阵 ... -
对java中的线程感想
2010-05-04 12:13 1037对java中的线程感想 1.进程和线程的区别 通俗一 ... -
javabean标签库的解释说明
2010-03-26 16:29 1258javabean标签库的解释说明 在JavaServer Pa ... -
一个体现Java接口及工厂模式优点的例子
2010-03-26 11:06 3455这篇文章我也不知道再哪看到的了,感觉写得还不错,转载了.... ... -
JDBC访问所有数据库的完整步骤
2010-03-25 09:31 1548JDBC访问所有数据库的完整步骤 1 将数据库的JDBC驱动加 ... -
Java中throws和throw的区别
2010-03-24 15:10 3155java处理异常方式 ... -
JSP常用内置对象使用说明
2010-03-22 09:32 1378内置对象特点: 1. 由JSP规范提供, ...
相关推荐
子进程不论有无自己的 vma,“它的” vma 都有对于物理页的映射,但它们共同映射的这些物理页属性为只读,即 Linux 并未给子进程真正分配物理页,当父子进程任何一方要写相应物理页时,导致缺页异常的写时复制。...
1. 为什么需要异常处理? 异常处理的存在是为了防止程序在遇到不可预期的问题时崩溃。例如,当尝试除以零时,会抛出`ArithmeticException`。通过异常处理,我们可以捕获这些错误,避免程序立即终止,并有机会执行...
4. **异常过滤**: VS还允许你为特定进程、线程或模块设置异常处理。这在多线程或多进程应用中特别有用,你可以只关注关心的部分,避免被其他不相关的异常中断。 5. **调试技巧**: 使用VS的断点和监视窗口可以更好地...
首先,我们要区分两种类型的异常:checked异常和unchecked异常。Checked异常是那些在编译时就需要被捕获或声明的异常,比如IOException和SQLException。它们通常代表了程序中可以预见的错误,需要调用者处理或传递给...
在异常响应的时候,处理器自动进行了返回地址的修正,但这个值并不一定就是异常处理返回后要执行的指令地址。 ARM 异常处理机制的知识点包括: * ARM 处理器的 7 种异常中断 * 异常处理机制的主要流程 * 异常响应...
1. Error:Error 是无法处理的异常,比如 OutOfMemoryError,一般发生这种异常,JVM 会选择终止程序,因此我们编写程序时不需要关心这类异常。 2. Exception:Exception 是另外一个非常重要的异常子类,比如 ...
在探讨Windows下的SEH(Structured Exception Handling,结构化异常处理)之前,我们先了解为何需要SEH。随着软件系统的复杂度日益提升,确保程序的健壮性和稳定性变得尤为重要。在Windows操作系统中,SEH作为一种...
总的来说,这个压缩包提供了海面高度异常的分析工具,涵盖了数据处理、可视化和文档说明,对于海洋学研究者、气候模型开发者以及关心地球气候变化的人来说,这些都是非常有价值的信息资源。通过深入理解和应用这些...
例如,如果输入的年龄不是整数,我们可以使用`raise ValueError('年龄必须为整数')`来抛出一个异常。 ```python try: age = int(input("年龄: ")) except ValueError as e: print("发生错误:", e) raise ...
异常处理机制通常由编译器和异常处理机制的运行时支持函数共同实现,因此,如何正确高效地实现异常处理机制是设计编译器和异常处理运行时支持函数所要关心的重要问题。 Java程序的编译运行有两种方式:在JVM上动态编译...
在传统的Java代码中,我们通常会捕获并处理这些检查异常,但在Lambda表达式中,这并不允许。 Lambda表达式只能抛出未检查异常(Unchecked Exception,如`RuntimeException`及其子类)或运行时异常。如果一个函数式...
在`catch`参数列表中,只需要指定异常类型,不关心具体实例的值。如果`try`块内的代码抛出异常,系统会尝试匹配`catch`块中的类型,找到匹配的`catch`块后,执行对应的异常处理代码。 在实际编程中,异常处理不仅...
最终的目标是无论发生什么错误,都可以通过`onError`事件进行统一处理,并由`ExceptionEngine`驱动器进行转化,确保UI只关心如何显示错误消息,而不是关心错误的具体来源。 通过以上方法,我们可以确保在Retrofit+...
在机器学习中,正样本通常指的是我们关心的目标类别,例如在异常检测中,正样本就是我们认为的异常行为或事件。这种方法与传统的二分类问题不同,后者通常包括正样本(异常)和负样本(正常)的平衡,而在异常检测中...
【胎儿附属物异常与胎儿异常】这一主题主要关注的是妊娠期间与胎儿健康相关的异常情况,特别是前置胎盘的问题。前置胎盘是指妊娠28周后,胎盘位于子宫下段,甚至覆盖宫颈内口,这可能导致严重的出血风险,对母婴健康...
GPS全球定位系统虽然在测绘和导航领域广泛应用,但其提供的高程数据是基于参考椭球面的大地高,而实际生活中我们更关心的是以似大地水准面为基准的正常高。因此,需要将大地高转换为正常高,这个转换的关键步骤就是...
最后,值得一提的是,在ES2019中,JavaScript引入了可选的catch绑定,允许在不关心异常信息的情况下,仅仅想要执行一些清理工作时省略catch参数: ```javascript try { // 可能抛出异常的代码 } catch { // 不...
1. 异常行为定义:首先,文档可能会定义什么是电子设备的“异常行为”,包括但不限于硬件故障、软件错误、通信问题等。 2. 异常检测技术:接着,文档会介绍各种用于检测电子设备异常的技术,如监控系统日志、数据...
总之,20210315-秒针系统-互联网行业:2020中国异常流量报告详细剖析了互联网广告行业的痛点,揭示了异常流量的严重性,并为解决这个问题提供了宝贵思路。这份报告对于所有关心互联网广告市场健康发展的人员来说,都...