Java编程中的检查型异常与非检查型异常分析
- 格式:pdf
- 大小:163.46 KB
- 文档页数:3
Java编程中的检查型异常与⾮检查型异常分析对于因为编程错误⽽导致的异常,或者是不能期望程序捕获的异常(解除引⽤⼀个空指针,数组越界,除零,等等),为
了使开发⼈员免于处理这些异常,⼀些异常被命名为⾮检查型异常(即那些继承⾃ RuntimeException 的异常)并且不需要进⾏声明。
Checked Exception和Unchecked Exception的⼏点不同之处
⽅法签名是否需要声明exception,调⽤该⽅法时是否需要捕获exception,exception产⽣的时候JVM控制程序的状态。
Sun 的“The Java Tutorial”观点
因为 Java 语⾔并不要求⽅法捕获或者指定运⾏时异常,因此编写只抛出运⾏时异常的代码或者使得他们的所有异常⼦类都继承⾃RuntimeException ,对于程序员来说是有吸引⼒的。
这些编程捷径都允许程序员编写 Java 代码⽽不会受到来⾃编译器的所有挑剔性错误的⼲扰,并且不⽤去指定或者捕获任何异常。
尽管对于程序员来说这似乎⽐较⽅便,但是它回避了Java 的捕获或者指定要求的意图,并且对于那些使⽤您提供的类的程序员可能会导致问题。
检查型异常代表关于⼀个合法指定的请求的操作的有⽤信息,调⽤者可能已经对该操作没有控制,并且调⽤者需要得到有关的通知 —— 例如,⽂件系统已满,或者远端已经关闭连接,或者访问权限不允许该动作。
如果您仅仅是因为不想指定异常⽽抛出⼀个 RuntimeException ,或者创建RuntimeException 的⼀个⼦类,那么您换取到了什么呢?您只是获得了抛出⼀个异常⽽不⽤您指定这样做的能⼒。
换句话说,这是⼀种⽤于避免⽂档化⽅法所能抛出的异常的⽅式。
在什么时候这是有益的?也就是说,在什么时候避免注明⼀个⽅法的⾏为是有益的?答案是“⼏乎从不。
”
换句话说,Sun 告诉我们检查型异常应该是准则。
该教程通过多种⽅式继续说明,通常应该抛出异常,⽽不是RuntimeException —— 除⾮您是 JVM。
在 Effective Java: Programming Language Guide⼀书中(请参阅参考资料),Josh Bloch 提供了下列关于检查型和⾮检查型异常的知识点,这些与 “The Java Tutorial” 中的建议相⼀致(但是并不完全严格⼀致):
第 39 条:只为异常条件使⽤异常。
也就是说,不要为控制流使⽤异常,⽐如,在调⽤ Iterator.next() 时⽽不是在第⼀次检查Iterator.hasNext() 时捕获NoSuchElementException 。
第 40 条:为可恢复的条件使⽤检查型异常,为编程错误使⽤运⾏时异常。
这⾥,Bloch 回应传统的 Sun 观点 —— 运⾏时异常应该只是⽤于指⽰编程错误,例如违反前置条件。
第 41 条:避免不必要的使⽤检查型异常。
换句话说,对于调⽤者不可能从其中恢复的情形,或者惟⼀可以预见的响应将是程序退出,则不要使⽤检查型异常。
第 43 条:抛出与抽象相适应的异常。
换句话说,⼀个⽅法所抛出的异常应该在⼀个抽象层次上定义,该抽象层次与该⽅法做什么相⼀致,⽽不⼀定与⽅法的底层实现细节相⼀致。
例如,⼀个从⽂件、数据库或者 JNDI 装载资源的⽅法在不能找到资源时,应该抛出某种ResourceNotFound 异常(通常使⽤异常链来保存隐含的原因),⽽不是更底层的IOException 、SQLException 或者NamingException 。
⼀、Java中异常概述
1.1Java异常结构
Throwable可以⽤来表⽰任何可以被作为异常抛出的类。
Throwable对象派⽣出两种类型:Error和Exception,前者⽤来表⽰编译时和系统错误,程序员往往不必关⼼;后者是可以被抛出的基本类型,需要程序员关注。
RuntimeException是Exception的派⽣类,不同点将在2.2与2.3⼩结中描述。
Java的异常(Exception)按照编译器检查⽅式⼜可以分为检查型异常(CheckedException)和⾮检查型异常(UncheckedException)。
1.2检查型异常(CheckedException)
在Java中所有不是RuntimeException派⽣的Exception都是检查型异常。
当函数中存在抛出检查型异常的操作时该函数的函数声明中必须包含throws语句。
调⽤改函数的函数也必须对该异常进⾏处理,如不进⾏处理则必须在调⽤函数上声明throws 语句。
检查型异常是JAVA⾸创的,在编译期对异常的处理有强制性的要求。
在JDK代码中⼤量的异常属于检查型异常,包括IOException,SQLException等等。
1.3⾮检查型异常(UncheckedException)
在Java中所有RuntimeException的派⽣类都是⾮检查型异常,与检查型异常相对抛出⾮检查型异常可以不在函数声明中
添加throws语句,调⽤函数上也不需要强制处理。
常见的NullPointException,ClassCastException是常见的⾮检查型异常。
⾮检查型异常可以不使⽤try...catch进⾏处理,但是如果有异常产⽣,则异常将由JVM进⾏处理。
对于RuntimeException的⼦类最好也使⽤异常处理机制。
虽然RuntimeException的异常可以不使⽤try...catch进⾏处理,但是如果⼀旦发⽣异常,则肯定会导致程序中断执⾏,所以,为了保证程序再出错后依然可以执⾏,在开发代码时最好使⽤try...catch的异常处理机制进⾏处理。
1.4异常的关键字
Java异常处理涉及到五个关键字,分别是:try、catch、finally、throw、throws
五个关键字的相关语法略。
⼆、异常处理⽅式
2.1异常链
在JDK1.4以后版本中,Throwable类⽀持异常链机制。
Throwable 包含了其线程创建时线程执⾏堆栈的快照。
它还包含了给出有关错误更多信息的消息字符串。
最后,它还可以包含 cause(原因):另⼀个导致此 throwable 抛出的 throwable。
它也称为异常链设施,因为 cause ⾃⾝也会有 cause,依此类推,就形成了异常链,每个异常都是由另⼀个异常引起的。
通俗的说,异常链就是把原始的异常包装为新的异常类,并在新的异常类中封装了原始异常类,这样做的⽬的在于找到异常的根本原因。
2.2异常的转译
异常转译就是将⼀种异常转换另⼀种新的异常并且再抛出的过程,异常转译的⽬的是将系统中出现的不同类型的异常进⾏型别的统⼀,以便于异常的统⼀处理。
绝⼤多数情况下转译出的“结果异常”类型都是⾃定义异常,并且在异常转译过程中需要将“原始异常”放置在异常链中。
2.3⾃定义异常
⾃定义异常就是⾃写的继承了Exception或RuntimeException的异常类。
实现⾃定义异常的⽬的⼤致可分为以下三种:
1.使⽤统⼀的类型标识多种不同型别的异常。
2.在产⽣异常时更好的进⾏信息传递。
常见的⼿段是在异常中定义异常码,异常信息,环境对象等字段。
3.将检查型异常转换为⾮检查型异常。
三异常处理
3.1关于检查型异常与⾮检查型异常的争论
在实际编程过程中使⽤检查型异常与⾮检查型异常的时机从JAVA语⾔产⽣的那⼀天开始就已经产⽣。
最为官⽅的说法可以参考Java最核⼼设计者之⼀JOSHUA BLOCH的《Effective Java》异常使⽤章节,他的主张是:对可恢复的情况使⽤检查型异常,对编程错误使⽤运⾏时异常。
虽然上述说法有着“皇家⾎统”但事实上在我看来Java的检查型异常是⼀个⾮常失败的作品,因为检查型异常具有超强
的“污染性”,它的出现所带来的⿇烦远⽐好处要多得多。
我的观点是:⼏乎在所有的情况下都不应当使⽤检查型异常。
当遇到检查型异常⽆法处理的情况时,应该使⽤异常转译转换为⾮检查型异常再抛出。
我⾮常兴奋的看到在Think in Java 4th Edition 上作者对这样的观点进⾏了详细的描述。
Java创造检查型异常的初衷是在编译期强制程序员对异常情况进⾏处理,从⽽使得程序更加的强壮可靠,可是Java的作者忘记了:好的程序设计语⾔能帮助程序员写出好程序,但⽆论那种语⾔都避免不了程序员⽤它写出坏程序。
对于异常处理的关键点并不在于是在编译期还是运⾏期对异常进⾏检查,⽽在于异常⼀定要检查并且需要建⽴统⼀的、⼀致的异常检查与处理模型。
3.2我的异常处理原则
1.仅处理当前可处理的异常。
2.对所有的检查型异常使⽤异常转译。
3.所有的⾃定义异常都是⾮检查型异常。
4.异常流程与正常流程进⾏分离,并尽可能的统⼀处理。
5.在⾮异常处理模块的catch块中尽可能不记⽇志。
6.除⾮是进⾏资源释放操作,否则catch块不应为空或者出现e.printTrace
7.finally块中不能出现复杂的操作,且不可以抛出异常,也不可以出现return。
3.3我处理异常的⼀般⽅式
1.将throw语句视为异常流程的起点,将Exception对象视作正常流程向异常流程跃迁过程中的数据载体。
2.建⽴统⼀的⾃定义异常类型,⽤以包装所有检查型异常。
3.⼤多数情况下仅在程序的主⼲上建⽴唯⼀的异常捕获点,并在这个点上对接收到的异常进⾏处理。
总结
以上是本⽂的全部内容,希望对⼤家有所帮助。