第九章 异常处理
程序在运行过程中发生错误或出现异常是不可避免 的,因此使用编程语言开发一个完整的应用系统时,在程序中应提供对出错或异常情况进行处理的策略。
9.1 异常处理的基本概念
异常(exception)是指在程序运行中由代码产生的一种错误。在不支持异常处理程序的程序设计语言中,每一个运行时的错误都必须由程序员手动控制。
9.1.1 错误与异常
当程序不能正常运行或者运行结果不正确时,表明程序中有错误。按照错误的性质将错误分为语法错、语义错和逻辑错三种。
1)语法错:又违反程序设计语言语法而产生的错误;
2)语义错:语法正确而语义 错误,不能被编译器所发现,只能在运行时才能被发现。一旦发现语义错,将停止程序的执行并指出错误的位置和性质;
3)逻辑错:如果程序编译通过,也可运行,但运行结果与预期结果不符。则称为逻辑错。
虽然有三种类型的错误,但是Java系统根据错误严重程度的不同,而将程序运行时的错误分为两类:错误和异常。
1)错误:是指程序在执行过程中所遇到的硬件或操作系统的错误;
2)异常:在硬件和操作系统正常工作时,程序遇到的运行错。有些异常是由于算法考虑不周引起的,有些是由于编程过程中的疏忽大意引发的。
9.1.2 Java语言的异常处理机制
1. 异常
异常是指程序在运行过程中发生的由于算法考虑不周或软件设计错误等导致的程序异常事件。java语言提供的异常处理机制是通过面向对象的方法来处理异常的。
2.抛出异常
在一个程序运行过程中,如果发生了异常事件,则产生代表该异常的一个“异常对象”,并把它提交给运行系统,再由运行系统寻找相应的代码来处理这一异常。生成异常对象并把它提交给运行系统的过程称为抛出异常。异常本身作为一个对象,产生一个异常就是产生一个异常对象。
这个对象可能由应用程序本身产生,也可能由Java虚拟机产生,这取决于产生异常的类型。该异常对象包含异常事件类型以及发生异常时应用程序目前的状态和调用过程等必要信息。
3.捕获异常
异常抛出后,运行系统从生成异常对象的代码开始,沿方法的调用栈逐层回溯查找,直到找到包含相应异常处理的方法,并把异常对象提交给该方法为止,这个过程称为捕获异常。
发现异常的代码可以抛出一个异常,运行系统捕获该异常,并交由程序员编写的相应代码进行异常处理。
9.2 异常处理类
Java语言的异常处理类是处理运行时的错误的特殊类,类中包含了该运行错误的信息和处理错误的方法等内容。
在“异常”类层次上的最上层有一个单独的类叫做Throwable,它是java.lang包中的一个类。这个类用来表示所有的异常情况,该类诞生了两个子类,java.lang.Error和java.lang.Exception。其中Error子类由系统保留,因为该类定义了那些应用程序通常无法捕捉的到的错误。Error类及其子类的对象,代表了程序运行时Java系统内部的错误;而Exception子类则是供应用程序使用的,它是用户程序能够捕捉到的异常情况。一般情况下,通过它的子类来创建自己的异常。
同其他类相同,Exception类有自己的属性和方法,它的构造方法有两个:
Public Exception();
public Exception(String s);
第二个构造方法可以接受字符串参数传入的信息,该信息通常是对该异常对应的错误的描述。
Exception类从父类Throwable那里继承了若干方法。常用的就有如下两个:
public String toString()——该方法返回描述当前Exception类信息的字符串;
public void printStackTrace()——该方法没有返回值,它的功能是完成一个输出操作,在当前的标准输出设备上输出当前异常对象的堆栈使用轨迹,即程序先后调用并执行了那些对象或类或方法,使得运行过程中产生了这个错误对象。
Throwable
错误类:虚拟机错误类(内存溢出错误类、栈溢出错误类)、连接错误类(类未找到错误类)、图形界面错误类等。
异常类:运行时异常类(算术异常类、空指针异常类、下标越界异常类、数组元素个数为负异常类、类型强制转换异常类、无效参数异常类)、非法访问异常类、图形界面异常类、类没有找到异常类、输入输出异常类(文件已结束异常类、文件未开始异常类)
程序对错误与异常的处理方式有三种:一是程序不能处理的错误;二是程序应避免而可以不去捕获的运行时异常;三是必须捕获的非运行异常。
9.3 异常的处理
在Java语言中,异常处理是通过try、catch、finally、throw、throws五个关键字来实现的。异常处理 的理论似乎非常繁琐,但是实际过程却并不复杂。
1.异常的产生
2.使用try-catch-finally语句捕获和处理异常
一般来说,系统捕获抛出的异常对象并输出相应的信息,同时终止程序的运行导致其后的程序无法运行。这其实并不是用户所期望的,incident即需要能让程序来接受和处理异常对象,从而不影响其他语句的运行,这就是捕获异常的意义所在。
Java语句的异常处理机制中,提供了try-catch-finally语句来捕获和处理一个或多个异常,其语法格式如下:
try
{
要检查的语句序列
}
catch(异常类名 形参对象名)
{
异常发生时的语句序列
}
finally
{
一定会运行的语句序列}
其中”要检查的语句序列“是可能产生异常的代码;”异常发生时的处理语句“是捕获到某种异常对象时进行处理的代码,catch后面的括号内的”形参对象名“为相应”异常类“的对象,其中”异常类“指的是由程序抛出的异常对象所属的类;”一定会运行的语句序列"是其必须执行的代码,无论是否捕获到异常。
下面介绍try-catch-finally语句的功能和处理异常的顺序
try块中代码可能会抛出一个或多个异常,则程序的运行便中断,并抛出由”异常“产生的”对象“。同时,该代码也指定它后面的catch语句所捕获的异常类型,try语句块用来启动Java的异常处理机制。可能抛出异常的语句包括,throw语句、调用可能出现的方法的方法调用语句,都应该在这个try语句块中;catch语句跟在try语句的后面,用来自定需要捕获的异常类型,当try语句块中的某条语句在执行时一旦出现异常,此时被启动的异常处理机制就会自动捕获到它,然后流程自动跳过产生异常的语句执行后面的语句。
3.多异常处理
catch块紧跟在try块的后面,用来接受try块可能产生的异常。一个catch语句块通常会用同种方式来处理它所接受到的所有异常,但是实际上一个try块可能产生多种不同的异常,如果希望能采取不同 的方法来处理这些不同的异常,就需要使用多异常才处理机制。多异常处理机制是通过一个try块后面定义若干个catch块来实现的,每个catch块用来接收和处理一种特定的异常对象。
当try对象抛出一个异常时,程序的流程首先转向一个catch块,并审查当前对象可否被这个catch块所接收。能接收是指能异常对象与catch后面的小括号中的参数类型相匹配,即catch所处理的异常类型与生成的异常对象的类型完全一致或是它的祖先类(catch括号中的异常类型应对应所产生的异常类或该类的祖先类)。
如果所有的catch块都不能与当前的异常对象匹配,则说明当前方法不能处理这个异常对象,程序流程将返回到调用该方法的上层方法。如果这个上层方法中定义了与产生异常相匹配的catch块,流程就跳转到这个catch块中,否则继续回溯到更上层的方法。
如果所有方法找不到合适的catch块,则由Java运行系统来处理这个异常对象。此时,通常会终止执行程序,退出JVM返回到操作系统,并在标准输出设别上输出相关的异常信息。
在另一种完全相反的情况下,假设try块中的所有语句都没有引发异常,则所有的catch语句都被忽略不被执行。
//filename: App9_2.java
public class App9_2
{
public static void main(String[] args)
{
int i;
int[] a= {1,2,3,4};
for(i=0;i<5;i++)
{
try
{
System.out.print("a["+i+"]/"+i+"="+(a[i]/i));
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.print("An out-of-bounds array subscript exception was caught");
}
catch(ArithmeticException e)
{
System.out.print("Exception class name is :"+e);
}
catch(Exception e)
{
System.out.print("Caught"+e.getMessage()+" Exception!");
}
finally
{
System.out.println(" finally i="+i);
}
}
System.out.println("Continue!");
}
}
说明:1)异常捕获过程中有两个判断,一是try程序块中是否有异常产生,二是产生的异常是否和catch块中括号内欲捕获的异常匹配。
2)catch块中的语句应根据异常类型的不同而执行不同的操作,比较通用的作法是输出异常的相关信息,包括异常名称、产生异常的方法等。
3)由于异常对象与catch块的匹配时按照catch块的先后顺序进行的,所以在处理多异常的过程中应该注意catch块的顺序。
4)当在try块中语句的代码抛出一个异常时,其后的代码不会执行。
5)finally块是可以省略的,若省略finally块,则在catch语句运行后,程序跳到后续继续执行。
6)当catch块中含有System.exit(0)语句时,则不执行finally块中的语句。
9.4 抛出异常
在捕获一个异常前,必须有一段代码生成一个异常对象并把它抛出。根据异常类型的不同,抛出异常的方法也是不同的。
1)系统自动抛出的异常;
2)指定方法抛出异常;
所有系统定义的运行时异常都可以由系统自动抛出。而制定的方法抛出异常需要使用关键字throw和throws来明确指定方法内抛出方法。如用户自定义的异常不可能依靠系统自动抛出,此时就必须借助于throw和throws语句。
1.抛出异常的方法与调用方法处理异常
在前面的学习中,异常的产生和处理都是在一个方法中进行的。但是实际编程中,有时并不需要由产生异常的方法自己处理,而需要在该方法之外进行处理。此时该方法应声明抛出异常,而由该方法的调用者负责处理,这时与异常有关的方法有两个:抛出异常的方法和处理异常的方法。
1)抛出异常的方法
如果在一个方法内部的语句执行时可能引发某种异常,但是并不能确定如何处理,则此方法应声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理,也就是说,方法中的异常没有用try-catch语句捕获和处理异常的代码。一个方法声明抛出异常有两种形式:
一,在方法体内使用throw语句抛出异常对象,其语法格式为:
throw 由异常所产生的对象;
二,在方法头部添加throws语句表示方法将抛出异常,带有throws子句的方法的声明格式如下:
[修饰符] 返回值类型 方法名 ([参数值列表]) throws 异常类列表
throws是关键字,异常类列表是方法中所要抛出的异常类
2)调用方法处理异常
当一个方法抛出异常后,该方法内又没有处理异常的语句,则系统就会将异常向上传递,再由调用它的方法来处理这些异常,若上层调用方法中仍没有处理异常的语句,则可以再往上追溯到上层。一直追溯到main()方法,这时JVM肯定要进行处理,这样编译就可以通过了。也就是说,如果某个方法产生了异常,则调用它的方法必须捕获并处理异常,否则就会出现错误。
//filename App9_3.java
public class App9_3
{
public static void main(String[] args)
{
int a=5,b=0;
try
{
if(b==0)
throw new ArithmeticException();
else
System.out.println(a+"/"+b+"="+a/b);
}
catch(ArithmeticException e)
{
System.out.println("Exception: "+e+"was thrown!");
e.printStackTrace();
}
}
}
2.由方法抛出异常交由系统处理
对于程序需要处理的异常,一般编写try-catch-finally语句捕获并处理,而对于程序无法处理必须交由系统处理的异常,由于系统直接调用的是主要方法main(),所以可以在主方法头使用throws子句声明抛出异常交由系统处理。
9.5 自定义异常类
系统自定义异常类主要是用来处理系统可以预见的较常见的运行时异常,对于某个应用程序特有的 运行时异常,则需要编程人员根据程序的特殊逻辑关系在用户程序中自己创建用户自定义的异常类和异常对象。
创建用户自定义异常时,一般需要进行如下操作:
1)声明一个新的异常类,用户自定义的异常类必须时Throwable类的直接或间接子类,Java推荐用户自定义异常类以Exception为直接父类,也可以使用某个已经存在的系统异常类或用户已定义的异常类为其父类。
2)为用户自定义的异常类定义属性和方法,或覆盖父类的属性和方法,使这些属性和方法能够体现该类的所对应的错误信息。
//filename:App9_7.java
class CircleException extends Exception
{
double radius;
CircleException(double r)
{
radius =r;
}
public String toString()
{
return "radius r="+radius +" not a positive integer";
}
}
class Circle
{
private double radius;
public void setRadius(double r) throws CircleException
{
if(r<0)
throw new CircleException(r);
else
radius=r;
}
public void show()
{
System.out.println("The area of circle is "+3.14*radius*radius);
}
}
public class App9_7
{
public static void main(String[] args)
{
Circle cir=new Circle();
try
{
cir.setRadius(-2.0);
}
catch(CircleException e)
{
System.out.println("DIY Exception :"+e.toString()+"");
}
cir.show();
}
}