什 么 是 例 外 、 例 外 处 理
例 外 就 是 在 正 常 情 况 以 外 的 事 件 。 Java 之 中 的 例 外 是 指 程 序 运 行 中 发 生 的 异 常 事 件 , 这 些 异 常事 件 将 阻 止 程 序 的 正 常 运 行 。 比 如 出 现 了 数 组 越 界 、 用户 输 入 错 误 等 等 。 而 顾 名 思 义 , 例 外 处 理 就 是 对 这 些 例外 进 行 处 理 。
所 有 的 高 级 计 算 机 编 程 语 言 里 都 有 判断 是 否 出 现 了 例 外 的 语 句 , 每 个 好 的 计 算 机 程 序 都 会 有对 例 外 进 行 处 理 的 程 序 段 。 只 不 过 在 不 同 的 计 算 机 编 程语 言 里 面 , 对 例 外 的 称 呼 不 同 , 例 如 有 的 语 言 里 所 讲 的错 误 处 理 , 其 实 就 等 同 于 Java 里 面 的 例 外 处 理 。 这 就 像诸 葛 亮 先 生 有 两 个 名 字 , 孔 明 是 他 诸 葛 亮 也 是 他 , 还 有人 称 呼 他 为 诸 葛 先 生 … … , 只 是 称 呼 不 同 罢 了 , 意 义 是一 样 的 。
抛 弃 之
在 许 多 高 级 语 言 中 都 是 通 过 使 用 if 语句 来 判 断 是 否 出 现 了 例 外 , 并 进 行 处 理 的 。 Java 作 为 一个 完 全 基 于 面 向 对 象 的 语 言 , 例 外 处 理 也 是 采 用 面 向 对象 的 方 法 。 在 一 个 方 法 的 运 行 过 程 中 如 果 发 生 了 例 外 ,则 这 个 方 法 将 生 成 一 个 代 表 该 例 外 的 对 象 , 并 把 它 提 交给 正 在 运 行 这 个 方 法 的 系 统 。 我 们 把 这 个 过 程 称 为 抛 弃一 个 例 外 。
就 像 抛 弃 这 个 球 一 样 , 把 它 给 抛 掉 了。 抛 弃 一 个 例 外 的 过 程 就 和 我 们 平 时 工 作 中 碰 到 了 特 殊情 况 无 法 解 决 , 需 要 写 一 份 报 告 交 把 这 个 棘 手 的 问 题 上报 上 级 主 管 部 门 一 样 。
捕 获 之
系 统 在 运 行 的 时 候 查 找 处 理 提 交 给 它的 例 外 的 方 法 , 这 个 过 程 称 为 捕 获 一 个 例 外 。 这 就 和 法官 判 案 时 查 找 相 应 的 法 律 条 文 的 过 程 是 一 样 的 。
优 越 性
1. 利 用 以 上 这 些 方 法 处 理 例 外 , 使 得例 外 处 理 的 程 序 代 码 与 普 通 的 程 序 代 码 不 再 混 为 一 体 ,减 少 了 编 程 序 的 工 作 量 , 同 时 也 增 加 了 程 序 可 读 性 。
2. 利 用 面 向 对 象 的 方 法 来 处 理 例 外 ,可 以 对 各 种 不 同 的 例 外 事 件 进 行 分 类 , 然 后 再 处 理 之 ,从 而 具 有 了 良 好 的 接 口 和 层 次 性 。
3. 利 用 类 的 层 次 性 既 可 以 区 分 不 同 的例 外 分 别 处 理 , 也 可 以 把 具 有 相 同 父 类 的 多 个 例 外 统 一处 理 , 具 有 相 当 的 灵 活 性 。
由 以 上 几 点 可 知 , Java 的 这 种 面 向 对 象的 例 外 处 理 机 制 为 那 些 具 有 动 态 运 行 特 性 的 复 杂 程 序 提供 了 强 有 力 的 控 制 方 式 。
以 上 这 些 优 越 性 背 下 来 就 可 以 了 , 到时 候 可 以 用 来 在 孩 儿 们 面 前 炫 耀 一 下 自 己 的 知 识 渊 博 。
与 例 外 处 理 相 关 的 类
用 面 向 对 象 的 方 法 来 进 行 例 外 处 理 首先 必 须 建 立 类 的 层 次 。 如 图 20.1 所 示 , 类 Throwable 是 最 顶层 的 , 只 有 它 的 子 类 才 可 以 作 为 一 个 例 外 被 抛 弃 。
运 行 时 与 非 运 行 时 例 外
Java 有 运 行 时 例 外 和 非 运 行 时 例 外 之 分。
所 谓 运 行 时 例 外 就 是 由 系 统 在 Java 程 序运 行 的 过 程 中 检 测 到 的 例 外 , 例 如 除 零 溢 出 ( 除 数 为 零导 致 的 错 误 ) 、 数 组 越 界 等 。 由 于 它 们 可 能 在 程 序 的 任何 位 置 发 生 , 而 且 谁 也 无 法 在 程 序 运 行 以 前 计 算 出 它 们会 发 生 多 少 次 , 所 以 Java 语 言 编 译 器 允 许 Java 程 序 不 对 它进 行 处 理 。
除 此 之 外 的 其 他 例 外 则 被 称 为 非 运 行时 例 外
。
图 20.1 例 外 处 理 — — 类 的 层
次
用 户 自 己 定 义 的 例 外 都 是 非 运 行 时 例外 。 然 而 并 非 所 有 非 运 行 时 例 外 都 是 用 户 自 己 定 义 的 例外 。
所 谓 用 户 自 己 定 义 的 例 外 , 就 是 你 在编 写 程 序 的 时 候 在 你 的 程 序 里 面 定 义 的 那 些 例 外 , 以 便使 得 你 的 程 序 具 有 更 高 的 可 靠 性 , 不 会 轻 易 地 出 差 错 。
Java 编 译 器 要 求 必 须 捕 获 或 声 明 所 有 的非 运 行 时 例
外 。 对 于 用 户 自 定 义 例 外 , 这 是 十 分 显 然 的; 否 则 , 系 统 就 不 知 道 这 些 用 户 自
定 义 的 例 外 的 特 性 。知 己 知 彼 才 能 百 战 百 胜 。
简 单 的 例 外 处 理
运 行 时 例 外 的 处 理
先 看 看 下 面 这 两 个 例 子 。
程 序 20.1
public class REP{
public static void main(String args[ ])
{
int AE = 1/0; // AE 等 于 1 除 以 0 , 将 导 致除 0 溢 出
例 外
}
}
程 序 20.2
public class REP{
public static void main(String args[ ])
{
int i = 0;
int AE = 1/i; // AE 等 于 1 除 以 i , 而 i 等于 0 , 也
将 导 致 除 0 溢 出
}
}
在 程 序 20.1 和 程 序 20.2 中 都 出 现 了 除 数为 0 导 致 的 “ 除 0 溢 出 ” 例 外 , 这 属 于 运 行 时 例 外 。 此 时, 运 行 程 序 的 系 统 会 自 动 把 生 成 的 例 外 对 象 交 给 缺 省 的( 也 就 是 默 认 的 ) 例 外 处 理 程 序 , 在 计 算 机 屏 幕 上 显 示出 这 个 例 外 的 内 容 及 发 生 此 例 外 的 位 置 。 在 JDK 环 境 下 运行 这 两 个 例 子 程 序 。 ( 有 关 JDK 开 发 环 境 的 介 绍 请 参 见 《开 发 环 境 》 一 章 )
程 序 20.1 的 运 行 结 果 为 :
c:\java\exam >javac
REP.java
REP.java:3: Arithmetic exception.
int AE = 1/0;
^
1 error
可 见 , 程 序 在 编 译 的 时 候 编 译 器 就 指出 了 错 误 。 此 程 序 不 能 通 过 编 译 。 无 法 生 成 可 以 运 行 的文 件 。
程 序 20.2 的 运 行 结 果 是 :
c:\java\exam>javac
REP.java
c:\java\exam>java
REP
java.lang.ArithmeticException: / by
zero
at REP.main(REP.java:4)
程 序 20.2 虽 然 通 过 了 编 译 , 编 译 器 没 有指 出 有 错 误 , 从 而 生 成 了 可 以 运 行 的 程 序 文 件 , 但 是 该程 序 在 运 行 时 系 统 指 出 出 现 了 例 外 。
非 运 行 时 例 外
我 们 也 是 先 看 一 个 例 子 :
程 序 20.3
class MyE extends Exception{ // 定 义 一 个 例 外 类
MyE
private int detailA,detailB;
MyE(int a,int b){
detailA = a;
detailB = b;
}
public String toString(){
return "MyException :
"+detailA+"<"+detailB;
}
}
static void compare(int a,int b) throws MyE
{
System.out.println("***************************");
System.out.println("call
compare("+a+","+b+")");
if (a< font>
System.out.println("normal exit :
"+a+"> = "+b);
}
try{
compare(a,b);
} catch(MyE e){
System.out.println("Catch
"+e);
}
finally{
System.out.println("return from
callcompare()");
}
}
callcompare(10,5);
callcompare(5,5);
callcompare(5,10);
}
}
程 序 20.3 抛 弃 了 自 己 的 例 外
。 上 例 对 两个 整 数 a 和 b 进 行 了 比 较 ( compare ) , 当 a ( = b 时 程 序 正常 运 行
, 当 a((b 时 程 序 抛 弃 例 外 MyException 。
在 JDK 里 例 20.3 的 运 行 结 果 如 下 :
c:\java\exam>java ED
***************************
call compare(10,5)
normal exit : 10> =
5
return from callcompare()
***************************
call compare(5,5)
normal exit : 5> =
5
return from callcompare()
***************************
call compare(5,10)
Catch MyException :
5<10
return from
callcompare()
例 外 的 抛 弃 、 捕 获 和 声
明
除 对 运 行 时 例 外 可 以 不 做 处 理 外 , 例外 处 理 还 有 以 下 几 种 方 法 :
- 使 用 throws 子 句 生 命 例 外 ;
- 定 义 自 己 的 例 外 类 , 并 用 throw 语 句 来 抛 弃 它;
- 使 用 try-catch-finally 语 句 捕 获 例 外 。
在 Java 语 言 中 捕 获 一 个 例 外 之 前 , 必 须有 一 段 Java 代 码 生 成 一 个 例 外 对 象 并 把 它 抛 弃 。 抛 弃 例外 的 代 码 可 以 是 你 自 己 Java 的 程 序 , JDK 中 的 某 个 类 , 或者 是 Java 运 行 时 系 统 。 这 就 像 在 蓝 球 场 上 打 球 一 样 , 你 若想 接 到 一 个 球 , 就 必 须 有 人 先 抛 出 这 个 球 , 不 管 这 人 是中 锋 , 还 是 后 卫 , 亦 或 是 对 方 球 员 。 同 理 , 如 同 所 有 人抛 球 都 必 须 用 手 一 样 , 无 论 是 你 自 己 的 程 序 还 是 运 行 时系 统 , 都 必 须 使 用 throw 语 句 来 抛 弃 例 外 。
例 如 , 在 例 20.3 的 compare 方 法 中 就 使 用了 throw 语 句 来 抛 弃 一 个 例 外 :
其 中 MyE 是 Exception 的 一 个 子 类 。
class MyE extends Exception{ // class
MyException }
由 throw 抛 弃 的 例 外 必 须 是 Throwable 类 或 其 子类 的 对 象
。
声 明 例 外
在 很 多 情 况 下 , 生 成 例 外 的 方 法 并 不需 要 处 理 它 , 而
是 用 throws 子 句 来 声 明 它 , 以 向 上 传 递 。如 例 20.3 中
static void compare(int a,int b) throws MyE
{
这 样 就 在 compare 方 法 中 声 明 了 例 外 MyE 。
一 个 方 法 中 也 可 以 同 时 声 明 多 个 例 外, 只 需 throws 在 后 列 出 所 有 要 声 明 的 例 外 即 可 。 例 如
static void Proc ( ) throws MyE ,
ArithmeticException{
… …
}
捕 获 例 外
捕 获 例 外 需 使 用 try-catch-finally 语 句 。
在 例 20.3 的 callcompare 方 法 中 , try 语 句 在 {} 中 指 定 了 一 段 代 码 , 该 代 码 可 能 会 抛 弃 几 个 例 外 ; 其后 的 catch 用 于 处 理 这 些 例 外 ; finally 则 提 供 了 统 一 的 出口 。
try{
compare(a,b);
}
catch(MyE e){
System.out.println("Catch
"+e);
}
finally{
System.out.println("return from
callcompare()");
}
}
程 序 20.4 import java.awt.*;
import java.applet.*;
class MyE extends Exception{ // class
MyException
private int detailA,detailB;
MyE(int a,int b){
detailA = a;
detailB = b;
}
public String toString(){
return "MyException :
"+detailA+"<"+detailB;
}
}
class Cons{ // 设 置 一 个 常 数 , 让 它 代 表 两 条线 之 间 的
距 离
final int jmp = 13;
}
public class ED extends Applet{ // class
Exception Demo
static void compare(int a,int b,Graphics g,int
Ln,int col) throws MyE {
Cons jump = new Cons();
g.drawString("***************************",col,Ln);
g.drawString("call
compare("+a+","+b+")",col,Ln+jump.jmp);
if (a< font>
throw new MyE(a,b);
g.drawString("normal exit
:"+a+"> = "+b,col,Ln+2*jump.jmp);
}
static void callcompare(int
a,int b,Graphics g,int Ln,int col){
Cons jump = new Cons();
try{
compare(a,b,g,Ln,col);
}
catch(MyE e){
g.drawString("Catch
"+e,col,Ln+2*jump.jmp);
}
finally{
g.drawString("return from
callcompare()",col,Ln+3*jump.jmp);
}
}
public void paint (Graphics
g){
Cons jump = new Cons();
int Ln;
int col;
Ln = 10; col = 20;
callcompare(10,5,g,Ln,col);
Ln = Ln+5*jump.jmp;
callcompare(5,5,g,Ln,col);
Ln = Ln+5*jump.jmp;
callcompare(5,10,g,Ln,col);
}
}
程 序 20.4 是 程 序 20.3 的
Applet 形 式 , 它 的运 行 结 果 如 图 20.2 所 示 。
图 20.2 程 序 20.4 的 执 行 结 果
几 件 值 得 注 意 的 事 情
- 自 定 义 的 例 外 类 的 类 名 通 常 可 以
以 Exception 结尾 。 例 如 :
MyException , MyArithmeticException 等 。 - 对 于 运 行 时 例 外 可 以 不 捕 获 、 声 明 , 而 提 交运 行 时 系 统 处 理 。
- 在 捕 获 或 声 明 例 外 时 , 要 选 取 合 适 类 型 与 层次 的 例 外 。
- 处 理 例 外 既 可 以 在 方 法 内 捕 获 并 处 理 , 也 可以 提 交 上 层 方 法 处 理 。
- 使 用 finally 语 句 可 为 例 外 处 理 提 供 统 一 的 出 口, 通 常 可 在 finally 语 句 中 进 行 资 源 清 除 工 作 , 如 关 闭 已打 开 的 文 件 等 。
- Java 采 用 面 向 对 象 的 例 外 处 理 机 制 减 小 了 编 程量 , 增 加 了 灵 活 性 , 增 强 了 程 序 的 可 读 性 和 可 靠 性 , 有利 于 编 写 具 有 动 态 运 行 特 性 的 复 杂 程 序 。
- 对 非 运 行 时 例 外 必 须 捕 获 或 声 明 。
对 于 自 定 义 的 例 外 类 , 通 常 作 为 类 Exception 的 子 类 , 而 不 作 为 类 Error 的 子 类 。 因 为 类 Error 通 常 用 于系 统 内 严 重 的 硬 件 错 误 。 - 抛 弃 的 例 外 必 须 是 Throwable 类 或 其 子 类 的 对 象。
- 注 意 区 分 throw 和 throws 的 用 法 :
throw 用 于 抛 弃 例 外 ;
throws 用 于 声 明 例 外 。