异常的处理主要包括捕获异常、程序流程的跳转和异常处理语句块的定义等。当一个异常被抛出时,应该有专门的语句来捕获这个被抛出的异常对象,这个过程被称为捕获异常。当一个异常类的对象被捕获后,用户程序就会发生流程的跳转,系统中止当前的流程而跳转至专门的异常处理语句块,或直接跳出当前程序和
Java
虚拟机,退回到操作系统。
8.3.1
异常类说明
Java
中所有的异常都由类来表示。所有的异常都是从一个名为
Throwable
的类派生出来的。因此,当程序中发生一个异常时,就会生成一个异常类的某种类型的对象。
Throwable
类有两个直接子类:
Exception
和
Error
。
与
Error
类型的异常相关的错误发生在
Java
虚拟机中,而不是在程序中。错误类(
Error
)定义了被认为是不能恢复的严重错误条件。在大多数情况下,当遇到这样的错误时,建议让该程序中断。这样的异常超出了程序可控制的范围。
由程序运行所导致的错误由
Exception
类来表示,该异常类定义了程序中可能遇到的轻微的错误条件。可以编写代码来处理这样的异常并继续执行程序,而不是让程序中断。它代表轻微的可以恢复的故障。接收到异常信号后,调用方法捕获抛出的异常,在可能的情况下再恢复回来,这样程序员可以通过处理程序来处理异常。
Java
中的异常类具有层次组织,其中
Throwable
类是
Error
类(错误类)和
Exception
类(异常类)的父类,
Throwable
类是
Object
类的直接子类。
异常类(
java.lang.Exception
)继承于
java.lang.Object
类中的
java.lang.Throwable
类。异常可分为执行异常(
Runtime Exception
)和检查异常(
Checked Exception
)两种,如图
8-1
所示。为了深入了解执行异常和检查异常内容,这里给出它们的详细介绍列举。
图
8-1
异常类的继承结构
1.执行异常
执行异常即运行时异常,继承于
Runtime Exception
。
Java
编译器允许程序不对它们做出处理。下面列出了主要的运行时异常。
·
ArithmeticException
:
一个非法算术运算产生的异常。
·
ArrayStoreException
:
存入数组的内容数据类型不一致所产生的异常。
·
ArrayIndexOutOfBoundsException
:
数组索引超出范围所产生的异常。
·
ClassCastException
:
类对象强迫转换造成不当类对象所产生的异常。
·
IllegalArgumentException
:
程序调用时,返回错误自变量的数据类型。
·
IllegalThreadStateException
:
线程在不合理状态下运行所产生的异常。
·
NumberFormatException
:
字符串转换为数值所产生的异常。
·
IllegalMonitorStateException
:
线程等候或通知对象时所产生的异常。
·
IndexOutOfBoundsException
:
索引超出范围所产生的异常。
·
NegativeException
:
数组建立负值索引所产生的异常。
·
NullPointerException
:
对象引用参考值为
null所产生的异常。
·
SecurityException
:
违反安全所产生的异常。
2.检查异常
除了执行异常外,其余的子类是属于检查异常类也称为非运行时异常,它们都在
java.lang
类库内定义。
Java
编译器要求程序必须捕获或者声明抛弃这种异常。下面列出了主要的检查异常。
·
ClassNotFoundException
:
找不到类或接口所产生的异常。
·
CloneNotSupportedException
:
使用对象的
clone(
)方法但无法执行
Cloneable所产生的异常。
·
IllegalAccessException
:
类定义不明确所产生的异常。
·
InstantiationException
:
使用
newInstance(
)方法试图建立一个类
instance时所产生的异常。
·
InterruptedException
:
目前线程等待执行,另一线程中断目前线程所产生的异常。
8.3.2
错误分类
Error
类与异常一样,它们都是继承自
java.lang.Throwable
类。
Error
类对象由
Java
虚拟机生成并抛出。
Error
类包括
LinkageError
(结合错误)与
VitualmanchineError
(虚拟机错误)两种子类。
1.
LinkageError
LinkageError
类的子类表示一个类信赖于另一个类,但是,在前一个类编译之后,后一个类的改变会与它不兼容。
LinkageError
类包括
ClassFormatError
、
ClassCircularityError
、
ExceptionInitializerError
、
NoClassDeFormatError
、
VeritfyError
、
UnsatisfidLinkError
和
IncompatibleClassChangeError
等子类。其中
NoIncompatibleClassChangeError
类又包含
AbstractMethodError
、
NoSuchField
Error
、
NoSuchMethodError
、
IllegalAccessError
和
InstantiationError
子类。这些类所代表的意义如下所述。
·
ClassFormatError
:
类格式所产生的错误。
·
ClassCircularityError
:
无限循环所产生的错误。
·
ExceptionInitializerError
:
初始化所产生的错误。
·
NoClassDeFormatError
:
没有类定义所产生的错误。
·
VeritfyError
:
类文件某些数据不一致或安全问题所产生的错误。
·
UnsatisfidLinkError
:
Java虚拟机无法找到合适的原始语言(
Native-Language)定义的方法所产生的错误。
·
IncompatibleClassChangeError
:
不兼容类所产生的错误。
·
AbtractMethodError
:
调用抽象方法所产生的错误。
·
NoSuchFieldError
:
存取或改变数据域所产生的错误。
·
NoSuchMethodError
:
调用类方法所产生的错误。
·
IllegalAccessError
:
不合法存取或改变数据域调用方法所产生的错误。
·
InstantiationError
:
使用抽象类或接口所产生的错误。
2.
VitualmachineError
当
Java
虚拟机崩溃了或用尽了它继续操作所需的资源时,抛出该错误。
VitualmachineError
包含
InternalError
、
OutOfMemoryError
、
StackOverflow Error
和
UnknownError
。这些类所代表的意义如下所述。
·
InternalError
:
虚拟机内部所产生的错误。
·
OutOfMemoryError
:
虚拟机内存不足所产生的错误。
·
StackOverflowError
:
堆栈无法容纳所产生的错误。
·
UnknownError
:
虚拟机不知名异常所产生的错误。
8.3.3
异常处理机制
Java
提供了一种独特的异常处理机制,通常通过异常来处理程序设计中可能出现的错误
。
在
Java
程序的执行过程中,如果出现了异常事件,就会生成一个异常对象;生成的异常对象将传递给
Java
运行系统,这一异常的产生和提交过程称为抛弃(
Throw
)异常。当
Java
运行系统得到一个异常对象时,它将会寻找处理这一异常的代码,找到能够处理这种类型异常的方法后,运行系统把当前异常对象交给这个方法进行处理,这一过程称为捕获(
Catch
)异常。如果
Java
运行系统找不到可以捕获异常的方法,则运行系统将终止,相应的
Java
程序也将退出。
Java
异常处理是通过
5
个关键字来管理的。它们是
try
、
catch
、
throw
、
throws
和
finally,
将在下面的小节中详细介绍。这里先大致给出它们的工作原理。
程序中需要被监测的程序语句序列应包含在一个
try
代码块中。如果
try
代码块中有异常发生,那么就要抛出该异常。可以用
catch
代码块来捕获这个异常,并且在
catch
代码块中加以适当的处理。系统产生的异常会由
Java
运行系统自动抛出。如果需要手动抛出异常,则使用关键字
throw
。在某些情况下,从一个方法抛出的异常必须用一个
throw
语句指定为异常。最后,从
try
代码块退出时,必须执行的代码要放在一个
finallly
代码块中。
异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。传统的处理异常的办法是:方法返回一个特殊的结果来表示出现异常,调用该方法的程序负责检查并分析函数返回的结果并进行相应的处理。但是,这样做有如下弊端:
·
函数返回
-
1代表出现异常,但是如果函数确实要返回
-
1这个正确的值时,就会出现混淆。
·
可读性降低,将程序代码与处理异常的代码交叉在一起。
·
由调用函数的程序来分析错误,这就要求客户程序员对库函数有很深的了解。
J
ava
的异常可以分为运行时异常和非运行时异常两类。继承于
RuntimeException
的类都属于运行时异常,例如算术异常、数组下标越界异常等。由于这些异常产生的位置是未知的,
Java
编译器允许程序员在程序中不对它们做出处理。除了运行时异常之外的其他由
Exception
继承来的异常都是非运行时异常,
Java
编译器要求在程序中必须处理这种异常
。
8.3.4
异常处理语句
异常处理的目的并不是为了避免发生异常,而是在异常发生时避免程序的异常终止,设法将损失降低到最小。
Java
的异常处理是通过
5
个关键词来实现的:
try
、
catch
、
throw
、
throws
和
finally
。一般情况下是用
try
来执行一段程序,如果出现异常,系统会抛出(
throw
)一个异常,这时候可以通过它的类型来捕捉(
catch
)它,最后(
finally
)由默认处理器来处理。
1.
try/catch语句块
在
Java
程序里,异常对象是依靠
try/catch
语句来捕获和处理的。
try/catch
异常处理语句分为
try
语句块和
catch
语句块,其格式如下。
try{
……
//try语句块,可能产生异常的多个语句
}catch{
……
//catch语句块,对异常进行处理
}
一般将可能产生异常情况语句放在
try
语句块中,这个
try
语句块用来启动
Java
的异常处理机制。凡是可能抛出异常的语句,包括
throw
语句和可能抛出异常的方法的调用语句,都应该包含在这个
try
语句块中。然后在
catch
语句块对异常进行处理。
Java
语言还规定,每个
catch
语句块都应该与一个
try
语句块相对应。
【例
8-1
】
捕获除数为零的异常,并显示相应信息。
class ArithmeticExceptionDemo{
public static void main(String
args[]) {
int zero,aInt;
try {// 监视可能产生异常的代码块
zero=0;
aInt=68/zero;
System.out.println("本字符串将不显示。
");
}catch (ArithmeticException e) {
//捕获
divide-by-zero错误
System.out.println("产生用零除错误。
");
}
System.out.println("在捕获语句后执行的一个语句。
");
}
}
该程序的执行结果如图
8-2
所示。
图
8-2
捕获
divide-by-zero错误的执行结果
Try
语句块中调用了可能抛出
ArithmeticException
的对象,
catch
语句块则专门用来捕获这类异常。可见,
catch
语句块应紧跟在
try
语句块的后面。当
try
语句块中的某条语句在执行时产生了一个异常,此时被启动的异常处理机制会自动捕获到它,然后流程自动跳过异常引发点后面的所有尚未执行语句,而转至
try
语句块后面的
catch
语句块,执行
catch
语句块中的语句。从执行的结果还可以看出,异常被捕获并执行完
catch
语句块中的语句后,继续执行
catch
语句块之后的语句。如果没有异常发生,则跳过
catch
语句块。
2.
finally语句块
finally
语句块用来控制从
try-catch
语句转移到另一部分之前的一些必要的善后工作,这些工作包括关闭文件或释放其他有关系统资源等。
finally
语句块中的语句是一种强制的、无条件执行的语句,即无论在程序中是否出现异常,无论出现哪一种异常,也不管
try
代码块中是否包含有
break
、
continue
、
return
或者
throw
语句,都必须执行
finally
语句块中所包含的语句。
finally
语句块紧接着
try-catch
结构中的最后一个
catch
语句块,其形式如下:
try{
……
}catch(Exception1 e1){
……
}catch(Exception2 e2){
……
} finally{
……
}
在出现和未出现异常的情况下都要执行的代码可以放到
finally
语句块中。加入了
finally
语句块后有以下
3
种执行情况。
·
没有抛出异常情况:
执行
try语句块后,再执行
finally语句块。
·
代码抛出在
catch
语句块中捕获的一个异常情况:
这时,
Java执行
try语句块中直到这个异常情况被抛出为止的所有代码,跳过
try语句块中剩余的代码;然后执行匹配的
catch语句块中的代码和
finally语句块中的代码。
·
代码抛出了一个在
catch
语句块中没有捕获到的异常情况:
这时,
Java执行
try语句块中直到这个异常情况被抛出为止的所有代码,跳过
try语句块中剩余的代码;然后执行
finally语句块中的代码,并把这个异常情况抛回到这个方法的调用者。
【例
8-2
】
带有
finally
语句块的程序示例。
class Finally_Demo{
public static void main(String
args[]) {
try{
int x=0;
int y=20;
int z=y/x;
System.out.println("y/x
的值是
:"+z);
}catch(ArithmeticException e){
System.out.println("
捕获到算术异常
:
"+e);
}finally{
System.out.println("
执行到
finally
块内
!
");
try{
String name=null;
if(name.equals("
王老三
")){
//
字符串比较
,
判断
name
是否为
"
王老三
"
System.out.println("
你
的名字叫王老三。
");
}
}catch(Exception e){
System.out.println("
又捕获到另一个异常
:
"+e);
}finally{
System.out.println("
执行到内层的
finally
块内
!
");
}
}
}
}
该程序的执行结果如图
8-3
所示。
图
8-3
带有
finally语句块的程序的执行结果
在
Java
语言中,
try-catch-finally
语句允许嵌套。本例就是将内层的一个
try
嵌套在外层的
finally
语句块内。在程序执行到外层的
try
语句块时,由于分母为零而产生了算术异常,所以程序转移到第一个
catch
语句块。该
catch
语句块捕获了这个算术异常,并进行了处理,之后再转向必须执行的外层的
finally
语句块。因为在该
finally
语句块内又产生了空指针异常(一个
null
字符串和字符串"王老三"进行比较),所以内层
catch
语句块又捕获到
NullPointerException
,最后程序转移到内层的
finally
语句块。
finally
语句块还可以和
break
、
continue
以及
return
等流程控制语句一起使用。当
try
语句块中出现了上述语句时,程序必须先执行
finally
语句块,才能最终离开
try
语句块。
【例
8-3
】
同时有
break
语句和
finally
语句块的程序的执行情况。
class FinallyWithBreakDemo{
public static void main(String args[]) {
for( ; ; )
try{
System.out.println("即将被
break中断,要退出循环了
!");
break;
}finally{
System.out.println("但是
finally块总要被执行到!
");
}
}
}
该程序的执行结果如图
8-4
所示。
图
8-4
同时有
finally语句块和
break语句的程序的执行结果
8.3.5
声明异常
在某些情况下,如果一个方法产生自己不处理或无法处理的异常,它就必须在
throws
子句中声明该异常。也就是说,在
Java
语言中如果在一个方法中生成了一个异常,但是这一方法并不确切地知道该如何对这一异常事件进行处理,这时,这个方法就应该声明抛弃异常,使得异常对象可以从调用栈向后传播,直到有适合的方法捕获它为止。
throws
关键字是在方法声明中用来列出从方法中发出的非起源于
Error
或
RutimeException
中的任何异常。能够主动引发异常的方法必须用
throws
来声明。通常使用
Java
预定义的异常类就可以满足程序开发者的编程需要。
声明抛弃异常是在一个方法中的
throws
子句中指明的。
下面是包含
throws
子句的方法的基本形式。
[修饰符
]
返回类型
方法名(参数
1,参数
2,……)
throws异常列表
{……
}
例如:
public int read ( ) throws IOException
{……
}
throws
子句中同时可以指明多个异常,说明该方法将不对这些异常进行处理,而是声明抛弃它们。例如:
public static void main (String args[ ])
throws IOException, IndexOutOf-
BoudsException
{……
}
【例
8-4
】
声明抛出异常的程序格式。
import java.io.*;
public class ExceptionExam5
{
public static void go() throws
IOException
{//方法代码
}
public static void main(String []
args)
{//程序入口主方法代码
}
}
因为考虑到
go()
方法可能产生一个
IOException
,而此时无法处理异常,所以要从
go()
方法抛出这个异常,并且需要用
throws
子句指定异常。另外,
Java
的
I/O
系统包含在
java.io
包中,因此
IOException
也包含在其中,所以使用语句“
import java.io.*;
”导入
java.io
包,然后可以直接引用
IOException
。
8.3.6
抛出异常
Java
应用程序在运行时如果出现了一个可识别的错误,就会产生一个与该错误相对应的异常类的对象。这个对象包含了异常的类型和错误出现时程序所处的状态信息,该异常对象首先被交给
Java
虚拟机,由虚拟机来寻找具体的异常处理者。在
Java
中把产生异常对象并将其交给
Java
虚拟机的过程称为抛出异常。
异常类不同,抛出异常的方法也不同,可以分为以下两种。
·
系统自动抛出的异常。
·
语句抛出的异常。
系统定义的所有运行异常都可以由系统自动抛出。例如,以非法的算术操作引发的算术异常,这时系统抛出已定义好的异常类
ArithmeticException
的对象。前面列出的例子中,基本都属于系统自动抛出的异常。
语句抛出的异常是借助
throw
语句定义何种情况产生这种异常。用户程序自定义的异常不可能依靠系统自动抛出,必须使用
throw
语句抛出这个异常类的新对象。系统定义的运行异常也可以由
throw
语句抛出。用
throw
语句抛出异常对象的一般步骤如下:
(
1
)指定或定义一个合适的异常情况类。
(
2
)产生这个类的一个对象。
(
3
)抛出它。
例如:
EOFException e = new EOFException( );
throw e;
使用
throw
语句抛出异常有两种方式:直接抛出和间接抛出。
1.直接抛出异常
直接抛出方式是直接利用
throw
语句将异常抛出,格式为:
throw
newExceptionObject;
利用
throw
语句抛出一个异常后,程序执行流程将直接寻找一个捕获(
catch
)语句,并进行匹配执行相应的异常处理程序,其后的所有语句都将被忽略。
【例
8-5
】
设计自己的异常类,从键盘输入一个
double
类型的数,若不小于
0.0
,则输出它的平方根;若小于
0.0
,则输出提示信息“输入错误”。
import java.io.*;
class MyException extends Exception{
void test(double x) throws MyException{
if(x<0.0) throw new MyException();
//条件成立时,执行
throw语句
else System.out.println(Math.sqrt(x));
}
public static void main(String args[]) throws IOException{
MyException n = new MyException();
try{
System.out.print("求输入实数的平方根。请输入一个实数:
");
BufferedReader br=
new BufferedReader(new
InputStreamReader(System.in) );
String s=br.readLine();
n.test(Double.parseDouble(s));
}catch(MyException e){
System.out.println("输入错误!
");
}
}
}
程序的两次运行结果如图
8-5
所示。
图
8-5
MyException类的两次运行结果
在这个程序中,定义的异常类通过
extends
子句继承了
Exception
异常类。在
test(
)
方法中,用
throw
语句指定了可能抛出的异常,该语句在参数小于
0
时被执行,产生并抛出异常
。
值得注意的是:在一个方法定义中如果采用了
throw
语句直接抛出异常,则该方法在发生异常的情况下可能没有返回值。本例就属于这种情况。
从本例也可以看出:由于系统不能识别用户自定义的异常,所以需要编程人员在程序中的合适位置创建自定义异常的对象,并利用
throw
语句将这个新异常对象抛出。
2.间接抛出异常
在
Java
程序中,可以在方法的定义中利用
throws
关键字声明异常类型而间接抛出异常。也就是说,当
Java
程序中方法本身对其中出现的异常并不关心或不方便处理时,可以不在方法实现中直接捕获有关异常并进行处理,而是在方法定义的时候通过
throws
关键字,将异常抛给上层调用处理。其形式如下。
public void myMethod1() throws
IndexOutOfBoundsException {
……
}
或
public void myMethod2() throws
myException1, myException2 {
……
}
在上层调用该方法时,必须捕获有关异常,否则编译时将会出错。例如,调用方法
myMethod2()
时,必须按如下方式进行。
try{
myMethod2
}catch (MyExceptionl e1){
……
}catch(MyException2 e2){
……
}
【例
8-6
】
带有间接抛出异常的类。
public class OutOfRangeException extends
Exception{
public
OutOfRangeException(){};
public
OutOfRangeException(Sting s){
super(s);
}
} // 定义一个异常类
import OutOfRangeException;
//装载异常类
import java.io.*;
public class CreatingExceptions{
private static BufferedReader in = new
BufferedReader
(new InputStreamReader(System.in));
public static void main
(String[] args) throws OutOfRangeException{
final int MIN = 25, MAX = 40;
int value;
OutOfRangeException problem =
new OutOfRangeException ("Input
value is out of range.");
//创建一个异常对象并可能抛出它
System.out.print ("Enter an integer
value between " + MIN +
" and " + MAX + ", inclusive: ");
try{
value = Integer.parseInt (in.readLine());
}catch (Exception exception) {
System.out.println ("Error reading int data,
MIN_VALUE value
returned.");
value = Integer.MIN_VALUE;
}
//确定该异常是否抛出
if (value < MIN || value >
MAX)
throw problem;
System.out.println ("End of main
method.");
//may never reach this place
}
}
这个例子有两个特征,一是它利用了自定义的异常类
OutOfRangeException
,一旦程序执行违背了所定义的逻辑就抛出这个异常;二是在抛出异常的方法中,利用
throws
关键字声明了
OutOfRangeException
异常类的间接抛出,这样若是在其他地方使用到这个类的对象,也可以捕获这个异常。
使用
throws
子句抛出异常时应注意如下两个问题。
·
一般这种抛出异常的语句应该被定义为在满足一定条件时执行,例如把
throws子句放在
if语句的条件分支中,只有当条件得到满足,即用户定义的逻辑错误发生时才抛出。例如,例
8-6中的条件(
value
< MIN || value > MAX)满足时,抛出异常。
·
对于含有
throws子句的方法,应该在方法头定义中增加如下部分。
throws异常类名列表
这样做主要是为了通知所有欲调用此方法的方法:由于该方法包含
throws了句,所以要准备接受和处理它在运行过程中可能会抛出的异常。如果方法中的
throws了句不止一个,方法头的异常类名表中的列出的异常也不止一个,应该包含所有可能产生的异常。例如,在上面的
myMethod2(
)
方法中包含的异常有:
myException1
和
myException2
。
注意:
执行
throws子语句将中断程序的执行,也就是说
throws的下一条语句将暂停执行。
8.3.7
自定义异常类
在实际的编程中并不一定非要使用
Java
已经定义的异常,经常需要创建自己的异常,以便指出编写的代码可能生成的一个特殊错误。创建自己的异常类,必须从一个现有的异常类型(最终继承自
Throwable
类)继承。继承一个异常同继承一个普通的类的方法是一样的
。
Java
提供的一些异常有时候不能满足编程的需求,例如规定用户输入数据的范围在
20
到
30
之间,但是
Java
并没有这个方面的异常,这时就可以应用自定义的异常来规范化客户的数据输入。
在
Java
中进行自定义异常时,自定义异常类必须是
Throwable
类的直接或间接子类。下面的例子是关于自定义异常的。它通过继承
Exception
类而继承
Throwable
类,即间接继承
Throwable
类。
【例
8-7
】
自定义异常类程序示例。
class OutBoundsException extends
Exception
{
OutBoundsException (String mes)
{
//
调用超类的构造函数
super(mes);
}
}
class check
{
String ChecktheNum(int n) throws OutBoundsException
{
Integer N=new Integer(n);
if(n>30||n<20)
throw
new OutBoundsException("the
number is out of bound!!");
else
return "the number"+N.toString()+"is in the bound!!";
}
}
class Test
{
public static void main(String []args)
{
try
{
check c=new check();
System.out.println("
以下是合法的数据的报告!
");
System.out.println(c.ChecktheNum(25));
System.out.println("
以下是非法的数据的报告!
");
System.out.println(c.ChecktheNum(5));
}
catch(OutBoundsException e)
{
System.out.println(e.toString());
}
}
}
运行结果如图
8-6
所示。
图
8-6
运行结果
注意:
一个方法所声明抛弃的异常是作为这个方法与外界交互的一部分而存在的。
所以,方法的调用者必须了解这些异常,并确定如何正确地处理它们。