### Java 的基本理念是 “ 结构不佳的代码不能运行 ” 。
### Java 使用异常来提供一致的错误报告模型,使得构件能够与客户端
代码可靠地沟通问题。
### 异常处理是 Java 中唯一正式的错误报告机制,并且通过编译器强制
执行。
》》概念
### Java 中的异常处理则建立在 C++ 的基础之上。
### 使用异常带来的好处之一是:往往能降低错误处理代码的复杂度。
》》基本异常
### 异常情形是指阻止当前方法或作用域继续执行的问题。
### 区分异常情形和普通问题:
》》普通问题是指,在当前环境下能得到足够的信息,总能处理这个
问题。
》》对于异常情形,就不能继续下去了,因为在当前环境下无法获得
必要的信息来解决问题。
碰到这种情形,所能做的就是从当前环境跳出,并且把问题提交给上
一级环境。这就是抛出异常时所发生的事情。
### 当抛出异常之后,有几件事会随之发生。首先,同 Java 中其他对象
的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径
(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。
此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行
程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态
中恢复,以使程序能要么换一种方式 运行,要么继续运行下去。
### 异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些
事务的底线。
### 还可以将异常看作一个内建的恢复(undo)系统,因为(在细心使用的情
况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,
异常将 “ 恢复 ” 到程序中某个已知的稳定点上。
### 异常最重要的一方面就是如果发生问题,它们将不允许程序沿着其正常的
路径继续走下去。
### 异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了
什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
### 异常参数:
@@ 所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受
字符串作为参数,以便能把相关信息放入异常对象的构造器。
@@
throw new NullPointerException("t= null")
关键字 throw : 在使用 new 创建了异常对象之后,此对象的引用将传递给
throw
### 异常返回的 “ 地点 ” 与普通方法调用返回的 “ 地点 ” 完全不同。(异常将在一
个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,
也可能会跨越方法调用栈的许多层次)。
### 能够-抛出任意类型的 Throwable 对象,它是异常类型的根类。通常,对于
不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者
异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常(通常,
异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。)
》》捕获异常
### 要明白异常是如何被捕获的,必须首先理解监控区域的概念。它是一般可能
产生异常的代码,并且后面跟着处理这些异常的代码。
### try 块:
try {
..........
}
### 异常处理程序
@@ 异常处理程序紧跟在 try 块之后,以关键字 catch 表示:
try {
.............
}catch( Type1 id1 ){
...............
}catch( Type1 id2 ){
.....................
}
@@ 异常处理程序必须紧跟在 try 块之后。当异常被抛出时,异常
处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后
进入 catch 子句执行,此时认为异常得到了处理。一旦 catch 子句
结束,则处理程序的查找过程结束。
@@ 注意在 try 块的内部,许多不同的方法调用可能会产生异常类型相
同的异常,而你只需要提供一个针对此类型的异常处理程序。
### 终止与恢复
### 异常处理理论上有两种基本模型: 终止模型和恢复模型。
### 但是恢复模型不实用,主要原因可能是它所导致的耦合:恢复性的
处理程序需要了解异常抛出的地点,这势必要 包含依赖于抛出位置的
非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从
许多地方抛出的大型程序来说,更是如此。
》》创建自定义异常
### 要自己定义异常类,必须从已有的异常类来继承,最好是选择意思相近的
异常类继承(不过这样的异常类并不容易找)。
### 对异常来说,最重要的部分就是类名。
### try {
...............
} catch (Exception e ) {
. e.printStackTrace ( System.out ) ; // 信息被发送到了 System.out ,
并自动地被捕获和显示在输出中
e.printStackTrace ( ) ; // 信息将被输出到标准错误流(System.err)
}
### 异常与记录日志
@@ 使用 java.util.logging 工具将输出记录到日志中。
》》异常说明
### 异常说明使用了附加的关键字 throws
void f() throws TooBig {
}
补充:使用关键字 throws ,声明该方法可能抛出异常(实际上不一定会产生异常)
### 使用 throws 声明的异常会在编译时被强制检查,这种异常称为被检查的异常。
》》捕获所有异常
### catch( Exception e){
}
@@ Exception 将捕获所有的异常,所以最好把它放在处理程序列表的末尾,以防
它抢在其他处理程序之前先把异常捕获了。
### 栈轨迹
@@ printStackTrace() 方法所提供的信息可以通过 getStackTrace() 方法来直接访问,
这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的
一帧。元素 0 是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable
被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。
### 重新抛出异常
@@ 有时候需要把刚捕获的异常重新抛出,尤其是在使用 Exception 捕获所有的异常的
时候。
catch(Exception e){
System.out.println("An Exception was thrown");
throw e ;
}
@@ 重抛异常会把异常给上一级环境中的异常处理程序,同一个 try 块的后续 catch 子句
将都被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常处理的
程序可以从这个异常对象中得到信息。
@@ 如果只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常
抛出点的调用栈信息,而并非新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()
方法,这将返回一个 Throwable 对象,它是通过对当前调用栈信息填入原来那个异常对象而建立
的。
catch(Exception e){
System.out.println("An Exception was thrown");
throw (Exception)e.fillInStackTrace() ; // 执行这一行就成了异常的新发生地了
}
@@ 异常对象都是用 new 在堆上创建的,垃圾回收器会自动把他们删除掉。
### 异常链
@@ 常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常信息保存下来,这被
称为异常链。
@@ 现在 Throwable() 的子类在构造器中都可以接受一个 cause (因由 )对象作为参数。这个
cause 就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建
并抛出了新的异常 ,也能通过这个异常链追踪到异常最初发生的位置。
@@ 在 Throwable() 的子类中,只有三种基本的异常类提供了带cause 参数的构造器。它们是
Error(用于Java 虚拟机报告系统错误)、Exception 和 RuntimeException 。如果要把其他类
型的异常链接起来,应该使用 initCause() 方法而不是构造器。
》》Java标准异常
@@ Throwable 这个 Java 类被用来表示任何作为异常被抛出的类。
@@ Throwable 对象可分为两种类型: Error 和 Exception
@@ Error 用来表示编译时和系统错误(除特殊情况外,一般不用关心)
@@ Exception 是可以被抛出的基本类型,在 Java 类库、用户方法以及运行时故障中都可能
抛出 Exception 型异常。
@@ 对异常来说,关键是理解概念以及如何使用。
@@ 异常并非全是在 java.lang 包里面定义的;有些异常是用来支持其他像 util 、 net 和 io
这样的程序包的。
## 特例:RuntimeException
@@ 如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常。
@@ RuntimeException 异常被称为“ 不受检查的异常 ” ,这种异常不需要再异常说明的声明方法
中抛出。RuntimeException 异常属于错误,将被自动捕获,就不用你亲自动手了。
@@ RuntimeException 异常,编译器不需要异常说明,其输出被报告给了 System.err
@@ 如果 RuntimeException 异常没有被捕获而直达 main() ,那么在程序退出前将调用异常的
printStackTrace() 方法。
@@ 请务必记住:只能在代码中忽略 RuntimeException (及其子类)类型的异常,其他类型异常
的处理都是由编译器强制实施的。究其原因,RuntimeException 代表的是编程错误:
-----无法预料的错误。比如从你控制范围之外传递进来的 null 引用。
-----作为程序员,应该在代码中进行检查的错误。在一个地方发生的异常,常常会在另一个地方
导致错误。
@@值得注意的是:不应把 Java 的异常处理机制当成是一个单一用途的工具。它被设计用来处理一些
烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;然而,它对于某些编译
器无法检测到的编程错误,也是非常重要的。
》》使用 finally 进行处理
@@ 对于一些代码,可能会希望无论 try 块中的异常是否抛出,它们都能得到执行。这通常适用于
内存回收之外的情况(因为回收由垃圾回收器完成)。为了达到这种效果,可以在异常处理程序
后面加上 finally 子句。完整的异常处理程序如下:
try{
..........................
}catch( ){
........................
}catch( ){
......................
}finally {
.....................
}
## finally 用来做什么
@@ 对于没有垃圾回收和析构函数的自动调用机制的语言来说,finally 非常重要。
@@ Java 有垃圾回收机制,所以内存释放不再是问题(Java 也没有析构函数)。
那么,Java 在什么情况下才能用到 finally 呢?
当要把除内存回收之外的资源恢复到它的初始状态时,就要用到 finally 子句。
这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,
甚至可以是外部世界的某个开关。
@@ 在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高
一层的异常处理程序之前,执行 finally 子句。
@@ 当涉及 break 和 continue 语句的时候,finally 子句也会得到执行。
请注意:如果把 finally 子句和带标签的 break 和 continue 配合使用,在 Java
里就没有必要使用 goto 语句了。
## 在return 中使用 finally
@@ 在try 块的末尾使用 return ,finally 子句也会得到执行。
@@ 因为 finally 子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以
保证重要的清理工作仍旧会执行。
@@ 在 finally 类内部,从何处返回无关紧要。
## 缺憾: 异常丢失
@@ 情况一:
try{
LostMessage lm = new LostMessage();
try{
lm.f(); //该方法会抛出一个异常a
}finally{
lm.dispose(); //该方法会抛出一个异常b
}
}catch( Exception e){
System.out.println(e);
}
说明:上述程序中的 异常a 不会被外层的 catch 捕捉到(丢失了),外层的 catch
只能捕获到 finally 子句里面的 异常b
@@ 情况二:
try{
throw new RuntimeException();
}finally {
return;
}
说明:上述程序运行时即使抛出了异常,也不会产生任何输出。
》》异常的限制
@@ 当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常 。
@@异常限制对构造器不起作用。子类的构造器可以抛出任何异常,而不必理会基类
构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用,
派生类构造器的异常说明必须包含基类构造器的异常说明。
@@ 派生类构造器不能捕获基类构造器抛出的异常。
@@ 在继承过程中,编译器会对异常说明做强制要求。但是,异常说明本身并不属于
方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能
基于异常说明来重载方法。
@@ 一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。
这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,
在继承和覆盖的过程中,某个特定方法的 “ 异常说明的接口 ” 不是变大了而是变小了
---------------这恰好和类接口在继承时的情形相反
》》构造器
@@ 在设计异常时有一个问题: 应该把异常全部放在这一层处理,还是先处理一部分,然后再
向上层抛出相同的(或新的)异常;又或者是不做任何处理直接向上层抛出。
( 如果用法恰当的话,直接向上层抛出的确能简化编程)
@@ java 的缺陷:除了内存的清理之外,所有的清理都不会自动发生。所以必须告诉客户端
程序员,这是他们的责任。
@@ 对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的
try 子句。
补充:上面通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:
在创建需要清理的对象之后,立即进入一个 try--finally 语句块。
》》异常匹配
@@ 抛出异常的时候,异常处理系统会按照代码的书写顺序找出 “ 最近 ” 的处理程序。找到
匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
@@ 查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可
以匹配其基类的处理程序。
@@ 如果把捕获基类的 catch 子句放在最前面,以此想把派生类的异常全给“ 屏蔽 ” 掉。这样
的话,编译器会向你报告错误。
》》其他可选方式
@@ 异常处理的一个重要原则是 “ 只有在你知道如何处理的情况下才能捕获异常 ”。
@@ 异常处理的一个重要目标是:把错误处理的代码和错误发生的地点相分离。
@@ 下面将会提到“ 被检查的异常 “ 及其并发症,以及采用什么方法来解决这些问题。
## 历史
@@ C++ 从 CLU 那里还带来了另一种思想:异常说明。
异常说明可能有两种意思:一个是” 我的代码会产生这种异常 , 这由你来处理 “
另一个是” 我的代码忽略了这些异常,这由你来处理 “
@@ 在Java 中,对于范型用于异常说明的方式存在着一些限制。
## 观点
@@ Java 发明了” 被检查的异常 “
@@ 反射和泛型就是用来补偿静态类型检查所带来的过多限制。
@@ 要理解编译器的能力限制。
@@ 自动构建过程和单元测试是非常重要的。
## 把异常传递给控制台
@@ 最简单而又不用写多少代码就能保护异常信息的方法,就是把它们从 main()
传递到控制台
@@ main() 作为一个方法也可以有异常说明。通过把它传递给控制台,就不必在
main() 里写 try-catch 子句了。
## 把 “ 被检查的异常 ” 转换为 “ 不检查的异常 ”
@@ 把” 被检查的异常 “包装进 RuntimeException 里面,如下:
try{
..................
}catch ( ThisException e){
throw new RuntimeException(e) ;
}
@@
法一: 用 RuntimeException 来包装‘ ” 被检查的异常 “
法二: 创建自己的 RuntimeException 的子类
》》异常使用指南
## 应该在下列情况下使用异常:
1)、 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常)
2)、解决问题并且重新调用产生异常的方法。
3)、进行少许修补,然后绕过异常发生的地方继续执行。
4)、用别的数据进行计算,以代替方法预计会返回的值
5)、把当前运行环境下能做的事情尽可能做完,然后把相同的异常重抛到更高层
6)、把当前运行环境下能做的事情尽可能做完,然后把不同的异常重抛到更高层
7)、终止程序
8)、进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人)
9)、让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资)
》》总结
@@ 异常处理的优点之一:就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理
你编写的这段代码 中产生的错误。
@@ Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过诸如 C++ 这类
语言的长处之一。