在开发项目过程中,核心业务代码只占20%~30%左右的时间,而用于开发容错代码的时间却高达70%~80%,这大大降低了开发效率,java中提供了异常处理机制可以很好的在一定程度上解决这个问题。
本章包括:异常处理基本知识、异常的层次结构、异常的匹配、定义自己的异常、再次抛出异常、断言等内容。
11.1 异常处理的基本知识
由于硬件错误,资源耗尽以及输入错误的数据等,都可能导致程序运行时产生异常,本节介绍捕获与处理异常的基本语法,包括try/catch/finally语法的基本使用、异常的传播过程及开发中需要注意的问题。
11.1.1 try和catch捕获异常
- 捕获并处理异常的语法
- try-catch语句的执行流程
1、捕获并处理异常的语法:
可以使用try-catch语句捕获并处理异常,try语句块用于指出可能出现异常的区域,随后跟上一个或多个catch语句块,在catch语句块中编写相应的异常处理程序。语法:
*try
{
//可能出现异常的代码
}catch(异常类型1 引用)
{
//异常类型1的处理代码
}
//........
catch(异常类型n 引用)
{
//异常类型n的处理代码
}*
try语句块只能有一个,而catch语句块可以任意多个;
catch语句块紧跟try语句块之后,而catch语句块必须相互紧跟(catch之间没有其他代码(注释除外))eg:
public class Test
{
public static void main(String[] args)
{
try
{
int[] a=new int[3];
a[2]=9;
}catch(ArrayIndexOutOfBoundsException aiobe)
{
System.out.println("数组下标越界");
}catch(NullPointerException npe)
{
System.out.println("空引用");
}
}
}
2、 try-catch语句的执行流程:
通过一个demo说明try-catch语句在有与没有异常情况下的执行流程eg:
public class Test
{
public static void main(String[] args)
{
try
{
int[] a=new int[4];
System.out.println("整型数组创建完毕。。");
a[3]=9;//访问数组元素
System.out.println("整型数组中第四个元素的值为:"+a[3]+"..");
}catch(ArrayIndexOutOfBoundsException aiobe)
{
System.out.println("异常:数组下标越界");
}catch(NullPointerException npe)
{
System.out.println("异常:空引用");
}
System.out.println("主程序正常结束。。。");
}
}
第一个catch 语句块是对”ArrayIndexOutOfBoundsException “进行处理的代码,aiobe是指向可能捕获到的ArrayIndexOutOfBoundsException 异常对象的引用,通过其可以访问捕获到的异常对象。
第二个catch语句块是对”NullPointerException“进行处理
运行结果:
try中并不一定发生异常,而是有可能发生异常的代码,若不发生异常,便正常执行,不会去执行相应的catch语句块。
将上面的代码”a[3]=9;“改为下面内容a[4]=9;
再次运行结果为:
try语句块中的代码从抛出异常处中断执行。
然后进入catch;
经过catch语句块捕获并处理异常后,代码恢复正常执行,主程序正常结束。
若将”int[] a=new int[4];“修改为:int[] a=null;
再次运行结果为:
从图中可以看出,由于使用空引用访问数组对象而抛出异常。
提示:在同一个try语句块中可以有多句可能抛出相同异常的语句,异常处理代码只需要编写一次,eg:try语句块中的代码有两处可能抛出相同的异常,只需要编写一个处理此类型异常的catch语句块,不管是哪里抛出异常,都将立即停止执行,转移至处理该异常的异常处理程序继续执行。
11.1.2 异常的传播过程
如果没有catch语句块捕获异常,异常将沿着方法的调用栈一直向上传播,如果传播中一直没有catch语句块捕获,则最终传播到main()方法,最后从main()方法抛出,由java运行时环境(JRE)来处理。eg:
public class Test
{
public static void main(String[] args)
{
method1();
}
static void method1()
{
method2();
}
static void method2()
{
int[] a=new int[3];
a[4]=12;
System.out.println("ok");
}
}
运行结果:
异常产生以后,其后面的代码不再执行。
因为一直没有catch语句捕获并处理这个异常,最终传播到了调用栈最上面的main()方法。
提示,可以自己调用异常对象的”printStackTrace()“方法来打印异常栈的信息,eg:”npe.printStackTrace()”,其中npe为异常引用。
下面图给出上述代码的方法调用栈与异常传播过程。
- 方法调用过程就像一个栈,先被调用的方法后返回,后被调用的方法先返回
11.1.3 finally语句块的使用 - finally语句块的基本使用规则
- finally语句块的作用;
1、finally语句块的基本使用规则:
*finally
{
//finally块中的代码
}*
finally语句块最多只有一个,可以没有。
finally语句块在try-catch语句中应该紧跟最后一个catch语句块。eg:
public class Test
{
public static void main(String[] args) {
try {
int[] a = new int[4];
a[3] = 9;
} catch (NullPointerException npe) {
System.out.println("空引用异常。。");
} finally {
System.out.println("finally语句块,无论是否抛出异常,这里总能执行");
}
}
}
运行结果:
2、finally语句块的作用:
一旦异常抛出,其后面的代码将不再执行,而是进入异常处理程序,如果没有异常处理程序,异常将沿方法调用栈一直向上传播,这在某些情况下将不能满足要求。
有一些特殊代码要求无论抛出异常与否都必须保证执行,eg:打开了一个数据库连接,无论处理中是否抛出异常,最后都要保证关闭连接,不能由于抛出异常而影响其执行,否则将引起系统崩溃,而在try-catch语句情况下降无法实现。因为:
将这些代码放在try中,则出现异常无法执行;
放在catch中会因为不出现异常而不执行;
如果try-catch都放呢?也不行,因为系统运行中可能会出现意料之外的异常,这些异常会一直向上传播,而不再指向。
finally语句块正是用来解决这样的问题,finally语句块无论在什么情况下都将保证执行。eg:
public class Test
{
p