第 6章 异常处理错误是编程中不可避免和必须要处理的问题。编程人员和编程工具处理错误的能力在很大程度上影响着编程工作的效率和质量。到目前为止,但前面章节里的程序没有包含处理异常的代码。如果程序在运行过程中发生了异常,那么系统就会以相应的错误消息终止程序的执行。如果因为程序的错误或者某些外部因素导致系统终止用户丢失数据,那程序就无法满足用户的需求。在程序发生异常时,程序应该能做到:通知用户程序出现了一个错误;保存全部工作;允许用户安全地退出程序。
对于异常的情况,例如可能造成程序崩溃的错误输入,
Java使用,异常处理,的错误捕获机制来进行处理。
本章要点
异常和异常类;
检查和非检查异常;
异常处理;
异常处理技巧;
创建自己的异常类;
6.1 异常和异常类异常是指发生在正常情况以外的事件,如用户输入错误、除数为零、
需要的文件不存在、文件打不开、数组下标越界、内存不足等。程序在运行过程中发生这样或那样的错误及异常是不可避免的。然而,一个好的应用程序,除了应具备用户要求的功能外,还应具备能预见程序执行过程中可能产生的各种异常的能力,并把处理异常的功能包括在客户程序中。也就是说,我们在设计程序时,要充分考虑到各种意外情况,不仅要保证应用程序的正确性,而且还应该具有较强的容错能力。这种对异常情况给予恰当的处理技术就是异常处理。
用任何一种程序设计语言的设计程序在运行时都可能出现各种意想不到的事件或异常的情况,计算机系统对于异常的处理通常有两种方法:
一是计算机系统本身直接检测程序中的错误,遇到错误时终止程序运行。
二是由程序员在程序设计中加入处理异常的功能。它又可以进一步区分为没有异常处理机制的程序设计语言中的异常处理和有异常处理机制的程序设计语言中的异常处理两种。
6.1 异常和异常类
Java语言的特色之一是异常处理机制( Exception Handling)。 Java语言采用面向对象的异常处理机制。通过异常处理机制,可以预防错误的程序代码或系统错误所造成的不可预期的结果发生,并且当这些不可预期的错误发生时,异常处理机制会尝试恢复异常发生前的状态或对这些错误结果做一些善后处理。通过异常处理机制,减少了编程人员的工作量,
增加了程序的灵活性,增强了程序的可读性和可靠性。
Java对异常的处理是面向对象的。在 Java中,预定义了很多异常类,每个异常类都代表了相应的错误,当产生异常时,如果存在一个被异常类与此异常相对应,系统将自动生成一个异常类对象。
所有的异常类都是从 Throwable类派生而来的。 Throwable类被包含在
java.lang包中,图 XXX显示了 Java异常类的层次结构。
6.1 异常和异常类
6.1 异常和异常类
Throwable类不能直接使用,在 Throwable类中定义了方法来检索与异常相关的错误信息,并且打印显示异常发生的栈跟踪信息。它包含有两个直接子类,Exception类和 Error类。
Error类及其所有子类用来表示严重的运行错误,比如内存溢出,一般无法在程序中进行恢复和处理。因此,我们不会用到它。 Exception类及其所有子类定义了所有能够被程序恢复和处理的标准异常,在编程中,我们要处理的异常主要是这一类。 Exception类拥有两个构造函数:
public Exception();和 public Exception(String s)。其中第二个构造函数中的字符串参数 s表示对该异常的描述说明。
Exception类的所有子类又可以分成两种类别,RunTimeException异常和其他异常。 RunTimeException异常表示异常产生的原因是程序中存在错误所引起的。如数组下标越界、空对象引用,只要程序中不存在错误,这类异常就不会产生。其他的异常不是由于程序错误引起的,
而是由于运行环境的异常、系统的不稳定等原因引起的。这一类异常应该主动地去处理。
6.2 已检查和未检查的异常
Java语言规范将任何 Error的子类以及 RuntimeException的子类都称为未检查
( unchecked)异常。而其他的异常都被称为已检查( checked)异常。
在 Java程序中,无论何时使用 java.io包中类的输入或输出方法,我们都会使用 throws IOException子句。如果没有在这些方法头中包括 throws子句,编译器就将生成语法错误。但是,我们并不担心诸如被 0除或者数组索引出界的情况。如果在程序执行期间发生这些类型的错误,那程序以相应的错误消息终止。对于这些类型的异常,我们无需在方法头中包括 throws子句。所以,在程序中,哪些类型的异常需要在方法头中包括 throws子句呢?
IOException是已检查异常,由于 System.in.read方法可能会引发 IOException
异常,因而抛出的是已检查异常。当编译器遇到这些方法调用时,会检查程序是否处理 IOException,或通过抛出异常来报告。启用编译器检查类似
IOException或其他类型的已检查异常,可以帮助客户程序减少不能正确处理的异常的数量。到目前为止,由于前面章节的程序不要求处理 IOException或其他类型的已检查异常,所以程序通过抛出它们来声明检查异常。
6.2 已检查和未检查的异常编译程序,当编译器或许无法确定是否发生诸如被 0除或索引出界的异常时,
因此,这些类型的异常(未检查异常)不会被编译器检查出。于是,为了提高程序的正确性,编程人员必须检查这些类型的异常。
由于编译器不检查未检查异常,所以程序无须使用 throws子句声明它们,也不需要在程序中提供代码来处理它们。属于 RuntimeException类的子类的异常是未检查异常。如果程序中不提供代码处理未检查异常,那就由 Java的默认异常处理程序来处理异常。
在方法头重,throws后面列出了方法可能抛出的各类异常。 throws 子句的语法是:
throws ExceptionType1,ExceptionType2…
其中的 ExceptionType1,ExceptionType2等都是异常类的名称。例如,考虑下面的方法:
public static void exceptionMethod() throws
NumberFormatException,IOException{ //statements}
6.3 异常处理
Java语言的异常处理是通过 try,catch,finally,throw和
throws语句来实现的。
6.3 异常处理 — try-catch-finally语句在大多数情况下,系统预设的异常处理方法只会输出一些简单的提示到控制台上,然后结束程序的运行。这样的处理方式在许多情况下并不符合我们的要求。为此,Java语言为我们提供了 try-catch-finally语句,使用该语句可以明确地捕捉到某种类型的异常,并按我们的要求加以适当的处理,这是充分发挥异常处理机制的最佳方式。
try-catch-finally组合语句用来实现抛出异常和捕获异常的功能,其一般语法格式如下:
6.3 异常处理 — try-catch-finally语句
try{
statement //可能发生异常的程序代码
}
catch(ExceptionType1 objRef1){
exception handling //处理异常的程序代码 1
}
catch(ExceptionType2 objRef2){
exception handling //处理异常的程序代码 2
}

catch(ExceptionTypeN objRefN){
exception handling //处理异常的程序代码 N
}
finally{
finally handling //无论是否发生异常都要执行的程序代码
}
6.3 异常处理 — try-catch-finally语句注意下面有关 try-catch-finally语句的规定,
将可能出现错误的代码放在 try块中,对 try块中的程序代码进行检查,可能会抛出一个或多个异常。因此,try后面可跟一个或多个 catch。
如果 try块中没有抛出异常,所有与 try块相关的 catch块将被忽略,程序在最后的 catch块后继续执行。
如果 try块中抛出异常,try块中的剩余语句将被忽略。程序以它们在 try块后显示的顺序搜索 catch块,并查找适当的异常处理程序。如果抛出的异常类型与其中一个 catch块中的参数类型相匹配,那就执行此 catch块的代码,还可忽略这个 catch块后面的剩余块。
如果最后的 catch块后面有 finally块,则不管是否发生异常,都会执行 finally
块。 finally块一般用来进行一些扫尾工作,如释放资源、关闭文件等。
6.3 异常处理 — 再次抛出异常
Java程序的异常十分复杂,在某些情况下,程序需要捕捉一个异常并且进行一些处理,但是却不能从根本上找到造成该异常的原因。 需要这种处理的一个典型情况是:当程序出现错误时,需要进行某些本地资源的释放工作,但仍不能完全解决出现的问题。这时,对于那些错误情况,需要调用那些处理错误的代码,然后再次调用 throw命令,重新抛出异常,使得异常重新回到调用链上。下面代码是一个较典型的例子:
Graphics g = image.getGraphics();
try{//可能抛出的异常代码; }
catch (MalformedURLException e)
{ g.dispose();
throw e;
}
6.3 异常处理 — 再次抛出异常上面的代码显示了导致程序必须再次抛出已捕获异常的一个最常见的原因。
如果不在 catch从句中对 g进行处理,那么它永远都不会被释放。而造成这样异常的根本原因:采用了错误格式的 URL却没有被解决。程序假设方法的调用者知道如何处理这样的异常,所以应该要把该异常传递给那些最终知道如何进行处理的程序模块。当然也可以抛出一个和捕获的异常类型不同的异常:
try
{ Obj a = new Obj();
a.load(s);
a.paint(g);
}
catch (RuntimeException e)
{ //产生另外一个 OBJ错误
throw new Exception(“OBJ error”);
}
6.4 异常处理技巧在 Java程序发生异常时,编程人员通常有几种选择,例如终止程序;从异常中恢复继续执行程序;或者记录错误并继续执行。
在某些情况下,当程序发生异常时,最好是让程序终止。如编写了一个程序,
程序准备从文件中输入数据,但在程序执行过程中输入的文件并不存在,那么继续执行该程序将没有任何意义。在这种情况下,程序可以输出相应的错误信息并终止。
在其他情况下,希望处理异常并使程序继续执行。如有一个数字作为输入的程序。如果用户输入一个字符来替代数字,程序将抛出
NumberFormatException。在类似情况下,程序可以保护必要的代码,一直提示用户输入正确的数字,直至输入有效为止。如:
try
{ Obj a = new Obj();
a.l ad(s);
a.paint(g);
}
catch (RuntimeException e)
{ //产生另外一个 OBJ错误
throw new Exception(“OBJ error”);
}
6.4 异常处理技巧
lag = false;
do{
try{
System.out.print(“输入一个整数:,);
number = Integer.parseInt(inText.getText());
flag = true;
}
catch(NumberFormatException ne){
System.out.println(“\n异常,+ne.toString());
}
}while(!flag);
继续执行 do… while循环以提示用户,直至用户输入有效的整数。
在创建用户自己的类或编写程序时,可能会发生异常,而
Java提供了相当多的异常类来处理这些异常。但 Java不提供用户程序所需的所有异常类。因此 Java允许编程人员创建自己的异常类以处理 Java的异常类未包含的异常。编程人员可以通过扩展 Exception或其子类(比如 IOException)来创建自己的异常类。
6.5 创建自己的异常类
6.5 创建自己的异常类 — 示例
public class myException extends Exception
{
public myException(){
super(“不能被 0除,);
}
public myException(String str){
super(str);
}
}
程序将创建自己的被 0除的异常类:
下面是主类 Example,要求从键盘输入被除数和除数,
并输出运算结果。
6.5 创建自己的异常类 — 示例
public class Example6_3
{ public static void main(String[] args){
Scanner reader = new Scanner(System.in);
double num = 0;
double deno = 0;
try{ System.out.println(“请输入被除数:,);
num = reader.nextDouble();
System.out.println(“请输入除数:,);
deno = reader.nextDouble();
if(deno = = 0.0)
throw new myException();
System.out.println(“商是:,+(num/deno));
}
catch(myException e){ System.out.println(“出现异常:,+e.toString());
}
catch(Exception e){
System.out.println(“出现异常:,+e.toString());
}}}
6.5 创建自己的异常类 — 示例请输入被除数,25
请输入除数,4
商是,6.25
程序运行结果 1:
请输入被除数,25
请输入除数,0
出现异常,myException
不能被 0除程序运行结果 2: