7.1 异常的基本概念
异常是导致程序中断运行的一种指令流,如果不对异常进行正确的处理,则可能导致程序的中断执行,造成不必要的损失,所以在程序的设计中必须要考虑各种异常的发生,并正确地做好相应的处理,这样才能保证程序正常的执行。在Java中一切的异常都秉着面向对象的设计思想,所有的异常都是以类和对象的形式存在,除了Java中已经提供的各种异常类之外,用户也可以根据需要定义自己的异常类。
提示
任何程序都可能存在问题。
在程序实际的应用中,可能存在大量的未知问题,所以在程序的开发中对于错误的处理是极其重要的,任何程序都是很难做到百分百完美,所以程序开发中一定要对各种问题进行处理,而Java提供的异常处理机制可以帮用户更好地解决这方面的问题。
7.1.1 为什么需要异常处理
在没有异常处理的语言中如果要回避异常,就必须使用判断语句,配合所想到的错误状况来捕捉程序里所有可能发生的错误。但为了捕捉这些错误,编写出来的程序代码经常有大量的判断语句,有时候这样也未必能捕捉到所有的错误,而且这样做势必导致程序运行效率的降低。
Java的异常处理机制恰好改进了这一点。它具有易于使用、可自行定义异常类,处理抛出的异常同时又不会降低程序运行的速度等优点。因而在Java程序设计时,应充分地利用Java的异常处理机制,以增进程序的稳定性及效率。
【例7.1】认识异常
程序执行结果:
在上面的程序中,因为除数为0,所以程序中出现了异常,从运行结果可以发现,如果不对异常进行处理的话,一旦出现了异常,程序就立刻退出,所以后面的两条语句并没有打印输出。此程序的执行流程如图7-1所示。
图7-1 程序执行流程图
提示
关于被除数不能为0的说明。
在计算机的发展中有两大计算机“杀手”,一个是断电,另外一个是除数为0。因为除数为0在数学上解是无穷大,对于计算机来说,如果是无穷大,则意味着内存将全部被占满。
如果想对保证上面的程序即使出现异常之后也可以正确执行,则就必须进行异常的处理了。
7.1.2 在程序中使用异常处理
在Java中异常处理语句有着自己的格式,如格式7-1所示。
【格式7-1 异常处理格式】
在格式7-1中已经明确地写出在try语句之中捕获可能出现的异常代码。如果在try中产生了异常,则程序会自动跳转到catch语句中找到匹配的异常类型进行相应的处理。最后不管程序是否会产生异常,则肯定都会执行到finally语句,finally语句就作为异常的统一出口。需要提醒读者的是,finally块是可以省略的。如果省略了finally块,则在catch块运行结束后,程序跳到try-catch块之后继续执行。异常的处理流程如图7-2所示。
【例7.2】对异常进行捕捉
程序执行结果:
从程序运行结果可以清楚地发现,因为程序中加入了异常处理代码,所以当有异常发生之后,整个程序也并不会因为异常的产生而中断执行。此程序的执行流程如图7-3所示。
图7-2 异常处理流程
图7-3 程序执行流程图
从图7-3中可以清楚的看见,所有的异常都在catch中处理了,catch处理完毕之后,程序正常结束。从格式7-1中可以发现,实际上在异常处理的最后有一个finally关键字,可以使用它作为异常的统一出口。
【例7.3】验证finally关键字
程序执行结果:
当然在程序的开发中肯定不会只存在一个异常,肯定会同时存在多种异常,此时,就需要使用多个catch语句进行处理。
【例7.4】使用初始化参数输入两个数字,并进行除法操作
此时,因为要使用初始化输入参数,所以在程序的执行时,输入以下的命令:
程序运行结果:
第1个参数的值是10,第2个参数的值是2,所以两数相除的结果为5。
提示
字符串转整型操作。
在以上的程序中有这样几段代码:
其中Integer.parseInt(字符串),这个方法是将输入的字符串变为int类型的数据,在第6章的包装类章节中已为读者详细介绍过。
上面的程序属于正确的运行,所以没有任何的问题,但是如果此时有以下的几种情况,则运行肯定会出现问题:
(1)没有输入参数或输入的参数不够,程序运行会出现以下的错误提示:
(2)运行时参数输入的不是数字,例如,使用以下的命令运行:
则程序运行时会出现以下的错误提示:
(3)输入的除数是0,此错误已经被捕捉了。
上面的程序实际上产生了3个比较明显的异常:
①数组超出绑定异常:ArrayIndexOutOfBoundsException。
②数字格式化异常:NumberFormatException。
③算术异常:ArithmeticException。
此时如果要想保持程序的执行正确,就必须同时对3个异常进行处理,所以此时的catch语句应该有3个,以分别处理不同的异常。
【例7.5】捕捉多个异常
上面的程序中使用了3个catch语句以分别处理3个不同的异常,但是每个程序的异常都使用这种方式处理的话则肯定很麻烦,因为在程序的开发过程中很难知道到底会有多少个异常。要想解决这个问题,首先要从异常的整体结构来分析。
提示
Java的异常处理格式也在不断改变。
在正常的异常处理中,异常处理的语句格式有try…catch和try…catch…finally。
但是随着Java的发展,对于异常的处理语句也可以采用try…finally的形式编写。
可以不写catch语法,但是这样的操作没有多大意义,有兴趣的读者可以自行研究。
7.1.3 异常类的继承结构
图7-4 异常结构
在整个Java的异常结构中,实际上有两个最常用的类:Exception、Error,这两个类全都是Throwable的子类,如图7-4所示。
- Exception:一般表示的是程序中出现的问题,可以直接使用try…catch处理。
- Error:一般指的是JVM错误,程序中无法处理。
一般情况下,开发者都比较习惯于把以上的两者统称为异常。而之前的算术异常、数字格式化异常等都属于Exception的子类。
提示
异常信息的输出方式。
在catch语句输出异常的时候,除了可以直接使用“System.out.println(异常对象)”的方式打印异常信息,有些时候也会直接使用Exception类中的printStackTrace()方法输出异常信息,代码如下所示:
使用这种方式打印的异常信息是最全的,所以在本书后面的有些章节也会使用上面的语法形式输出异常信息。
7.1.4 Java的异常处理机制
在整个Java的异常处理中,实际上也是按照面向对象的方式进行处理,处理的步骤如下:
(1)一旦产生异常,则首先会产生一个异常类的实例化对象;
(2)在try语句中对此异常对象进行捕捉;
(3)产生的异常对象与catch语句中的各个异常类型进行匹配,如果匹配成功,则执行catch语句中的代码。
以上过程如图7-5所示。
图7-5 异常的处理步骤
从之前学习过的对象多态性可以清楚地知道,所有的子类实例可以全部使用父类接收,那么就可以利用向上转型的概念,让所有的异常对象都使用Exception接收。
【例7.6】使用Exception处理其他异常
上面的程序在最后直接使用Exception进行其他异常的捕获,那么本程序出现的全部异常就都可以处理了。但是要注意,在Java中所有捕获范围小的异常必须放在捕获大的异常之前,否则程序在编译的时候就会出现错误提示。
【例7.7】错误的异常处理
程序执行结果:
上面程序的错误提示是“算术异常”,已经被捕捉了,因为Exception捕捉的范围最大,所以以后的全部异常都是不可能处理的,根据这样一个概念,一般在开发时,不管出现任何异常,都可以直接使用Exception进行处理,这样会比较方便。
【例7.8】使用Exception处理异常
上面的代码直接使用Exception进行异常的处理,所以任何的异常都可以非常方便地进行处理。
说明
提问:可不可以直接使用Throwable?
既然可以使用Exception方便地捕获所有异常,那么以后在程序中直接使用Throwable的类不是更好吗?
回答:不建议这样使用,最大只能捕获Exception。
首先使用Throwable捕获异常,这在代码中是没有任何的问题,因为Throwable捕获的范围是最大的。但一般开发中是不会直接使用Throwable进行捕获的,对于Throwable来说有Exception、Error两个子类,Error类本身不需要程序处理,而程序中需要处理的只是Exception,所以没必要使用Throwable。
另外,要提醒读者的是,对于一个程序,如果有多个异常最好分别进行捕获,而不要直接使用Exception捕获全部异常。