07_Java异常处理机制
本章章节
> 7.1 异常的基本概念
> 7.2异常类的继承架构
> 7.3抛出异常
> 7.4编写自己的异常类
> 7.5多态中异常的声明抛出原则
> 7.6异常使用规则
> 7.7 断言
.本章摘要:
(1)、抛出异常。
(2)、停止程序运行。
(1)、在程序中抛出异常。
(2)、指定方法抛出异常。
即使在编译时没有错误信息产生,但在程序运行时,经常会出现一些运行时的错误,这种错误对Java而言是一种异常。有了异常就要有相应的处理方式。本章将介绍异常的基本概念以及相关的处理方式。
Java的错误类型主要有以下3种(异常属于运行时错误):
语法错误(syntax errors):没有遵循java语言规则。
运行错误(runtime errors):发生一个不可以执行的操作。
逻辑错误(logic errors):没有按照预期的方案执行。
7.1 异常的基本概念
异常也称为例外,是在程序运行过程中发生的、会打断程序正常执行的事件,下面是几种常见的异常:
1、 算术异常(ArithmeticException)。
2、 没有给对象开辟内存空间时会出现空指针异常(NullPointerException)。
3、 找不到文件异常(FileNotFoundException)。
所以在程序设计时,必须考虑到可能发生的异常事件,并做出相应的处理。这样才能保证程序可以正常运行。
Java的异常处理机制也秉承着面向对象的基本思想。在Java中,所有的异常都是以类的类型存在,除了内置的异常类之外,Java 也可以自定义的异常类。此外,Java的异常处理机制也允许自定义抛出异常。关于这些概念,将在后面介绍。
7.1.1 为何需要异常处理?
在没有异常处理的语言中,就必须使用if或switch等语句,配合所想得到的错误状况来捕捉程序里所有可能发生的错误。但为了捕捉这些错误,编写出来的程序代码经常有很多的if语句,有时候这样也未必能捕捉到所有的错误,而且这样做势必导致程序运行效率的降低。
Java的异常处理机制恰好改进了这一点。它具有易于使用、可自行定义异常类,处理抛出的异常同时又不会降低程序运行的速度等优点。因而在Java程序设计时,应充分地利用Java的异常处理机制,以增进程序的稳定性及效率。
7.1.2 简单的异常范例
Java本身已有相当好的机制来处理异常的发生。本节先来看看Java是如何处理异常的。TestException7_1是一个错误的程序,它在访问数组时,下标值已超过了数组下标所容许的最大值,因此会有异常发生:
范例:TestException7_1.java
public class TestException7_1{ public static void main(String args[]){ int arr[]=new int[5]; // 容许5个元素 arr[10]=7; // 下标值超出所容许的范围 System.out.println("end of main() method !!"); } }
在编译的时候程序不会发生任何错误,但是在执行到第6行时,会产生下列的错误信息:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at TestException7_1.main(TestException7_1.java:6)
错误的原因在于数组的下标值超出了最大允许的范围。Java发现这个错误之后,便由系统抛出“ArrayIndexOutOfBoundsException”异常,用来表示错误的原因,并停止运行程序。如果没有编写相应的处理异常的程序代码,则Java的默认异常处理机制会先抛出异常、然后停止程序运行。
7.1.3 异常的处理
TestException7_1的异常发生后,Java便把这个异常抛了出来,可是抛出来之后没有程序代码去捕捉它,所以程序到第6行便结束,因此根本不会执行到第7行。如果加上捕捉异常的程序代码,则可针对不同的异常做妥善的处理。这种处理的方式称为异常处理。
异常处理是由try、catch与finally三个关键字所组成的程序块,其语法如下:
异常处理顺序:
1、可能出现异常的代码都应放在try代码块中。try程序块若是有异常发生时,程序的运行便中断,并抛出“异常类所产生的对象”。然后将程序控制转交给catch块。
2、抛出的对象如果属于catch()括号内欲捕获的异常类,则catch会捕捉此异常,然后进到catch块里继续运行。一个try块可以和多个catch块配合以处理多个异常。多捕获是要按照从小到大的顺序进行异常声明。
3、无论try程序块是否有异常产生,或者产生的异常是否与catch()括号里的异常相同,最后一定会运行finally块里的程序代码。finally通常用作资源的释放工作。
finally的程序代码块运行结束后,程序再回到try-catch-finally块之后继续执行。
由上述的过程可知,异常捕捉的过程中做了两个判断:第一个是try 程序块是否有异常产生,第二个是产生的异常是否和catch()括号内欲捕捉的异常相同。
值得一提的是,finally块是可以省略的。如果省略了finally块不写,则在catch()块运行结束后,程序跳到try-cath块之后继续执行。
根据这些基本概念与运行的步骤,可以绘制出如图7-1所示的流程图:
图7-1 异常处理的流程图
“异常类”指的是由程序抛出的对象所属的类,例如TestException7_1中出现的“ArrayIndexOutOfBoundsException”就是属于异常类的一种。至于有哪些异常类以及它们之间的继承关系,稍后将会做更进一步的探讨。下面的程序代码加入了try与catch,使得程序本身具有捕捉异常与处理异常的能力。
范例:TestException7_2.java
public class TestException7_2 { public static void main(String args[]) { try { // 检查这个程序块的代码 int arr[] = new int[5]; arr[10] = 7; // 在这里会出现异常 } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组下标越界!"); } finally { // 这个块的程序代码一定会执行 System.out.println("这里一定会被执行!"); } System.out.println("main()方法结束!"); } }
输出结果:
数组下标越界!
这里一定会被执行!
main()方法结束!
程序说明:
1、程序第7行声明一个arr的整型数组,并开辟了5个数据空间。
2、程序第8行为数组中的第10个元素赋值,此时已经超出了该数组本身的范围,所以会出现异常。发生异常之后,程序语句转到catch语句中去处理,程序通过finally代码块统一结束。
上面程序的第5~9行的try 块是用来检查是否会有异常发生。若有异常发生,且抛出的异常是属于ArrayIndexOutOfBoundsException类,则会运行第10~13行的代码块。因为第8行所抛出的异常正是ArrayIndexOutOfBoundsException类,因此第12行会输出“数组下标越界!”字符串。由本例可看出,通过异常的机制,即使程序运行时发生问题,只要能捕捉到异常,程序便能顺利地运行到最后,且还能适时的加入对错误信息的提示。
程序TestException7_2里的第10行,如果程序捕捉到了异常,则在catch括号内的异常类ArrayIndexOutOfBoundsException之后生成一个对象e,利用此对象可以得到异常的相关信息,下例说明了类对象e的应用:
范例:TestException7_3.java
public class TestException7_3 { public static void main(String args[]) { try { int arr[] = new int[5]; arr[10] = 7; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组下标越界!"); System.out.println("异常:" + e); // 显示异常对象e的内容 } System.out.println("main()方法结束!"); } }
输出结果:
数组下标越界!
异常:java.lang.ArrayIndexOutOfBoundsException: 10
main()方法结束!
例子中省略了finally块,但程序依然可以运行。在第10行中,把catch()括号内的内容想象成是方法的参数,而e就是ArrayIndexOutOfBoundsException类的对象。对象e接收到由异常类所产生的对象之后,就进到第11行,输出“数组下标越界!”字符串,而第12行则是输出异常所属的种类,也就是java.lang.ArrayIndexOutOfBoundsException。而java.lang正是ArrayIndexOutOfBoundsException类所属的包。
由前面的知识,我们知道,直接打印对象可以输出内容,说明该类重写了toString()方法。所以,前面我们直接打印e,其实就是e.toString()。
System.out.println(e); è System.out.println(e.toString());
除了可以直接打印异常类对象之外,还可以利用如下函数访问异常信息:
getMessage():返回该异常的详细描述字符串。
printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。
getStackTrace():返回该异常的跟踪栈信息。
各函数使用方法如下:
System.out.println(e.getMessage()); e.printStackTrace();
e.printStackTrace(System.out); System.out.println(e.getStackTrace());
7.1.4 异常处理机制的回顾
当异常发生时,通常可以用两种方法来处理,一种是交由Java默认的异常处理机制做处理。但这种处理方式,Java通常只能输出异常信息,接着便终止程序的运行。如TestException7_1的异常发生后,Java默认的异常处理机制会显示出:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at TestException7_1.main(TestException7_1.java:6)
接着结束TestException7_1的运行。
另一种是处理方式是自行编写的try-catch-finally块来捕捉异常,如TestException7_2与TestException7_3。自行编写程序代码来捕捉异常最大的好处是:可以灵活操控程序的流程,且可做出最适当的处理。图7-2绘出了异常处理机制的选择流程。
图7-2 异常处理的方法
7.2 异常类的继承架构
异常可分为两大类:java.lang.Exception类与java.lang.Error类。这两个类均继承自java.lang.Throwable类。图7-3为Throwable类的继承关系图。
图7-3 Throwable类的继承关系图
习惯上将Error与Exception类统称为异常类,但这两者本质上还是有不同的。Error类专门用来处理严重影响程序运行的错误,可是通常程序设计者不会设计程序代码去捕捉这种错误,其原因在于即使捕捉到它,也无法给予适当的处理,如JAVA虚拟机出错或栈溢出错误或内存泄漏错误等就属于Error。
不同于Error类,Exception类包含了一般性的异常,这些异常通常在捕捉到之后便可做妥善的处理,以确保程序继续运行,如TestException7_2里所捕捉到的ArrayIndexOutOfBoundsException就是属于这种异常。
从异常类的继承架构图中可以看出:Exception类扩展出多个子类,其中IOException、RuntimeException是较常用的两种。RuntimeException即使不编写异常处理的程序代码,依然可以编译成功,而这种异常必须是在程序运行时才有可能发生,例如:数组的索引值超出了范围。与RuntimeException不同的是,IOException一定要编写异常处理的程序代码才行,它通常用来处理与输入/输出相关的操作,如文件的访问、网络的连接等。
几种常见的RuntimeExcpetion如下:
ArrayIndexoutofBoundsException:数组下标越界异常。
ArithmeticException:算术异常。如整数被0除,运算得出的结果。
NullPointerException:空指针异常。当对象没被实例化时,访问对象的属性或方法。
当异常发生时,发生异常的语句代码会抛出一个异常类的实例化对象,之后此对象与catch语句中的类的类型进行匹配,然后在相应的catch中进行处理。
7.3 抛出异常
前两节介绍了try_catch_finally程序块的编写方法,本节将介绍如何抛出(throw)异常,以及如何由try-catch来接收所抛出的异常。抛出异常有下列两种方式:
1、程序中抛出异常
2、指定方法抛出异常
以下两小节将介绍如何在程序中抛出异常以及如何指定方法抛出异常。
7.3.1 在程序中抛出异常
当程序出现错误时,系统会自动抛出异常,除此之外,java也允许程序自行抛出异常,自行抛出异常使用throw语句完成。其语法格式如下:
throw 异常类实例对象;
可以发现在throw后面抛出的是一个异常类的实例对象,下面来看一个实例:
范例:TestException7_4.java
public class TestException7_4 { public static void main(String args[]) { int a = 4, b = 0; try { if (b == 0) throw new ArithmeticException("一个算术异常"); // 抛出异常 else System.out.println(a + "/" + b + "=" + a / b); // 若抛出异常,则执行此行 } catch (ArithmeticException e) { System.out.println("抛出异常为:" + e); } } }
输出结果:
抛出异常为:java.lang.ArithmeticException: 一个算术异常
程序说明:
1、程序TestException7_4是要计算a/b的值。因b是除数,不能为0。若b为0,则系统会抛出ArithmeticException异常,代表除到0这个数。
2、在try块里,利用第8行来判断除数b是否为0。如果b=0,则运行第9行的throw语句,抛出ArithmeticException异常。如果b不为0,则输出a/b的值。在此例中强制把b设为0,因此try块的第9行会抛出异常,并由第13行的catch()捕捉到异常。
3、抛出异常时,throw关键字所抛出的是异常类的实例对象,因此第9行的throw语句必须使用new关键字来产生对象。
7.3.2 指定方法抛出异常
如果方法内的程序代码可能会发生异常,且方法内又没有使用任何的代码块来捕捉这些异常时,则必须在声明方法时一并指明所有可能发生的异常,以便让调用此方法的程序得以做好准备来捕捉异常。也就是说,如果方法会抛出异常,则可将处理此异常的try-catch-finally块写在调用此方法的程序代码内。
如果要由方法抛出异常,则方法必须以下面的语法来声明:
方法名称(参数…)throws 异常类1,异常类2,…
范例TestException7_5是指定由方法来抛出异常的,如下所示:
范例:TestException7_5.java
public class TestException7_5 { public static void main(String[] args) { System.out.println("商为:" + div(10, 0)); } public static int div(int a, int b) throws Exception { return a / b; } }
编译结果:
TestExeption7_5.java:7: 未报告的异常java.lang.Exception;必须对其进行捕捉或声明以便抛出
System.out.println("商为:" + div(a, b));
^
1 error
程序说明:
如果在方法中用throws抛出一个Exception异常,则在调用它的地方就必须明确地用try-catch来捕捉,否则会编译报错。
解决办法:
在TestExeption7_5程序之中,如果在调用div函数的时候加上try-catch的语句对其进行捕捉可以解决。如下
public class TestException7_6 { public static void main(String[] args) { try { System.out.println("商为:" + div(10, 0)); } catch (Exception e) { System.out.println(e); } } public static int div(int a, int b) throws Exception { return a / b; } }
也可以将throws Exception换成throws ArithmeticException。
如果在main()方法后再用throws Exception声明的话,那么程序也是依然可以编译通过的。也就是说在调用用throws抛出异常的方法时,可以将此异常在方法中再向上传递,而main()方法是整个程序的起点,所以如果在main()方法处如果再用throws抛出异常,则此异常就将交由JVM进行处理了。
7.4 编写自己的异常类
在JDK中提供的大量API方法之中含有大量的异常类,但这些类在实际开发中往往并不能完全的满足设计者对程序异常处理的需要,在这个时候就需要用户自己去定义所需的异常类了,用一个类清楚的写出所需要处理的异常。为了处理各种异常,Java可通过继承的方式编写自己的异常类。因为所有可处理的异常类均继承自Exception类,所以自定义异常类也必须继承这个类。
自己编写异常类的语法如下:
class 异常名称 extends Exception { … }
Exception构造方法:public Exception(String message);
通常在自定义异常类的构造方法中调用父类Exception的构造方法:super("message");如有必要,还需要覆盖下列获得错误信息的方法:toString()、getMessage()、printStackTrace()
我们可以在自定义异常类里编写方法来处理相关的事件,甚至可以不编写任何语句也可正常地工作,这是因为父类Exception已提供相当丰富的方法,通过继承,子类均可使用它们。
接下来以一个范例来说明如何定义自己的异常类以及如何使用它们。
范例:TestException7_6.java
class DefaultException extends Exception { public DefaultException(String message) { super(message); // 调用Exception类的构造方法,存入异常信息 } public String toString() { return "自定义异常类的toString方法"; } public String getMessage() { return "自定义异常类的getMessage方法"; } public void printStackTrace() { System.out.println("自定义异常类的printStackTrace方法"); } } public class TestException7_6 { public static void main(String args[]) { try { double money = 8668.6d; if (money < Math.pow(2, 20)) { // 在这里用throw直接抛出一个DefaultException类的实例对象 throw new DefaultException("金额不足"); } } catch (DefaultException e) { System.out.println(e); // 显示异常对象e的内容 System.out.println(e.toString()); System.out.println(e.getMessage()); e.printStackTrace(); } System.out.println("main()方法结束!"); } }
7.5 多态中异常的声明抛出原则
在多态中,异常的声明抛出有一定的原则:
子类覆盖父类方法,子类抛出的异常必须为父类可视异常。即子类抛出的异常不能多于父类抛出的异常。例如:
class OneException extends Exception { } class TwoException extends Exception { } class ThreeException extends Exception { } class Father { void show() throws OneException, TwoException { } } public class Test extends Father { // 所抛出的异常不能多于父类抛出的异常 void show() throws TwoException, ThreeException { } public static void main(String[] args) { new Test().show(); } }
子类实现接口,如接口定义的方法抛出的异常存在交集,在子类实现中,不能声明抛出任何异常。例如:
class OneException extends Exception { } class TwoException extends Exception { } interface Football { void play() throws OneException; } interface Basketball { void play() throws TwoException; } public class Test implements Football, Basketball { // 如接口存在交集,不能声明抛出的异常 public void play() { } public static void main(String[] args) { new Test().play(); } }
7.6 异常使用规则
如果在方法内处理了异常则不需要抛出,如果需要调用者处理异常则需要使用throws抛出异常。如果多个类发生了共同异常可以考虑设计异常类。一般能用简单逻辑判断的不要使用异常,直接用if…else结构判断即可。不要过度使用异常、不要使用过于庞大的try块、不要忽略捕捉到的异常。
7.7 断言
断言是Java 1.4版新增的一个特性,并在该版本中增加了一个关键字assert。可以把断言功能看成是异常处理的高级形式。
所谓断言(Assertion)是一个Java语句,布尔表达式,程序员认为在程序执行时该表达式的值应该为true。系统通过计算该布尔表达式执行断言,若该表达式为false系统会报告一个错误。通过验证断言是true,能够使程序员确信程序。
为了提高性能,可能要关闭断言检查。通常在程序开发和调试阶段开启断言功能,在部署时关闭。
5.0以前的JDK缺省情况下,若使用assert作为标识符,编译器会给出警告(不是编译错误),从5.0以后将是一个错误。
使用断言:
·断言失败是致命的、不可恢复的错误。
·断言检查仅仅用在程序开发和测试阶段。
语法如下:
1、assert boolean条件
或者
2、assert boolean条件 : 详细信息
这两个形式都会对“条件”进行判断,“条件”是一个布尔表达式。如果判断结果为假(false)则抛出AssertionError。在第二种形式中,“详细信息”会传进AssertionError的构造函数中并转成一个消息字符串。
例如,如果要进行如下的计算时:
double y = Math.sqrt(x);
sqrt(x)是一个开平方运算,x必须为正才不会出错。为了检查传入的参数是否为正,可以使用如下的断言语句:
assert x >= 0;
double y = Math.sqrt(x);
或者
assert x >= 0 : "x >= 0";
double y=Math.sqrt(x);
当x为负值时, assert语句将抛出AssertionError异常,你就可以根据异常信息对程序的其它部分进行检查。
打开和关闭断言功能:
默认情况下,断言是关闭的。可以通过-enableassertions或者-ea选项来运行程序以打开断言:
java -ea Myapp
打开或者关闭断言是类装载器的功能。当断言功能被关闭时,类装载器会跳过那些和断言相关的代码,因此不会降低程序运行速度,即它们没有任何副作用。
可以使用-da选项来关闭断言功能:
java -da Myapp
·本章摘要:
1、程序中没有处理异常代码时,Java的默认异常处理机制会做下面的操作:
(1)、抛出异常。
(2)、停止程序运行。
2、异常处理是由try、catch与finally三个关键字所组成的程序块。
3、try程序块中若有异常发生时,程序的运行便会中断,抛出“由系统类所产生的对象”,并依下列的步骤来运行:
(1)、抛出的对象如果属于catch()括号内所要捕捉的异常类,catch会捕捉此异常,然后进到catch程序块里继续执行。
(2)、无论try程序块是否捕捉到异常,也不管捕捉到的异常是否与catch()括号里的异常相同,最后都会运行finally块里的程序代码。
(3)、finally中的代码是异常的统一出口,无论是否发生异常都会执行此段代码。
4、当异常发生时,有两种处理方式:
(1)、交由Java默认的异常处理机制去处理。
(2)、自行编写try-catch-finally块来捕捉异常。
5、异常可分为两大类:java.lang.Exception与java.lang.Error类。
6、RuntimeException可以不编写异常处理的程序代码,依然可以编译成功,它是在程序运行时才有可能发生的;而其它的Exception一定要编写异常处理的程序代码才能使程序通过编译。
7、catch()括号内,只接收由Throwable类的子类所产生的对象,其它的类均不接收。
8、抛出异常有下列两种方式:
(1)、在程序中抛出异常。
(2)、指定方法抛出异常。
9、程序中抛出异常时,要用到throw这个关键字。
10、如果方法会抛出异常(使用throws),则可将处理此异常的try-catch-finally块写在调用此方法的程序代码中。
感谢阅读。如果感觉此章对您有帮助,却又不想白瞟