![名师讲坛:Java开发实战经典(第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/824/26793824/b_26793824.jpg)
7.1 异常的基本概念
异常是导致程序中断运行的一种指令流,如果不对异常进行正确的处理,则可能导致程序的中断执行,造成不必要的损失,所以在程序的设计中必须要考虑各种异常的发生,并正确地做好相应的处理,这样才能保证程序正常的执行。在Java中一切的异常都秉着面向对象的设计思想,所有的异常都是以类和对象的形式存在,除了Java中已经提供的各种异常类之外,用户也可以根据需要定义自己的异常类。
提示
任何程序都可能存在问题。
在程序实际的应用中,可能存在大量的未知问题,所以在程序的开发中对于错误的处理是极其重要的,任何程序都是很难做到百分百完美,所以程序开发中一定要对各种问题进行处理,而Java提供的异常处理机制可以帮用户更好地解决这方面的问题。
7.1.1 为什么需要异常处理
在没有异常处理的语言中如果要回避异常,就必须使用判断语句,配合所想到的错误状况来捕捉程序里所有可能发生的错误。但为了捕捉这些错误,编写出来的程序代码经常有大量的判断语句,有时候这样也未必能捕捉到所有的错误,而且这样做势必导致程序运行效率的降低。
Java的异常处理机制恰好改进了这一点。它具有易于使用、可自行定义异常类,处理抛出的异常同时又不会降低程序运行的速度等优点。因而在Java程序设计时,应充分地利用Java的异常处理机制,以增进程序的稳定性及效率。
【例7.1】认识异常
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P219_124623.jpg?sign=1739258311-FJ3W7uKOj6Wc2YEaaKCHl3UAMrsVevXD-0-f9611302f13321bcaf62813bab31fb2a)
程序执行结果:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P220_124627.jpg?sign=1739258311-YiwCIY7PEg1iahPCNsgExNMmGaGLaCHX-0-17aae40c43a7d92ed4901a96456e240d)
在上面的程序中,因为除数为0,所以程序中出现了异常,从运行结果可以发现,如果不对异常进行处理的话,一旦出现了异常,程序就立刻退出,所以后面的两条语句并没有打印输出。此程序的执行流程如图7-1所示。
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P220_31681.jpg?sign=1739258311-qKf5ETnAYmSyeyqcVhPXG4x9RixnEqeR-0-b27759f653181cab5d203e1639204b6d)
图7-1 程序执行流程图
提示
关于被除数不能为0的说明。
在计算机的发展中有两大计算机“杀手”,一个是断电,另外一个是除数为0。因为除数为0在数学上解是无穷大,对于计算机来说,如果是无穷大,则意味着内存将全部被占满。
如果想对保证上面的程序即使出现异常之后也可以正确执行,则就必须进行异常的处理了。
7.1.2 在程序中使用异常处理
在Java中异常处理语句有着自己的格式,如格式7-1所示。
【格式7-1 异常处理格式】
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P220_124630.jpg?sign=1739258311-k6jCcKHV8C9mk04e4ugJy4CWnKSnLyMG-0-cd0607f4256dee4dd280ea69d6b8ee40)
在格式7-1中已经明确地写出在try语句之中捕获可能出现的异常代码。如果在try中产生了异常,则程序会自动跳转到catch语句中找到匹配的异常类型进行相应的处理。最后不管程序是否会产生异常,则肯定都会执行到finally语句,finally语句就作为异常的统一出口。需要提醒读者的是,finally块是可以省略的。如果省略了finally块,则在catch块运行结束后,程序跳到try-catch块之后继续执行。异常的处理流程如图7-2所示。
【例7.2】对异常进行捕捉
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P221_124632.jpg?sign=1739258311-rvg2TgrlVw8kaCffakPadaH0nJ1h7niz-0-11d0a6b59eb95899230659cbf9705f16)
程序执行结果:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P221_124633.jpg?sign=1739258311-m7pubeMYvrMo4vtlRlHZ7B40ilhmsl7k-0-90f0981c357a9a9039d45c2f7fbb5315)
从程序运行结果可以清楚地发现,因为程序中加入了异常处理代码,所以当有异常发生之后,整个程序也并不会因为异常的产生而中断执行。此程序的执行流程如图7-3所示。
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P221_31809.jpg?sign=1739258311-9nCvlpLY5WcYEsnCgQmtPzRkBGomql6K-0-7dcfbdeda2e62eeefb77e89bb9f5b72e)
图7-2 异常处理流程
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P221_31810.jpg?sign=1739258311-xG0kR7Im8Bj0MMGzd1qtDDrHeaCbA2Jk-0-a715efb7eb4bf4f715f7e481274bf325)
图7-3 程序执行流程图
从图7-3中可以清楚的看见,所有的异常都在catch中处理了,catch处理完毕之后,程序正常结束。从格式7-1中可以发现,实际上在异常处理的最后有一个finally关键字,可以使用它作为异常的统一出口。
【例7.3】验证finally关键字
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P221_124637.jpg?sign=1739258311-akIGKQvDPyt4ayXpXHzORnkCyPQ29dZe-0-14dee50e7427b7ee4468a198537e98f5)
程序执行结果:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P222_124640.jpg?sign=1739258311-B6z5xDbq4IjIxJEqb61ioJHwshHRQS3g-0-079f41f0d1f13f34a3d6e0756651aa2f)
当然在程序的开发中肯定不会只存在一个异常,肯定会同时存在多种异常,此时,就需要使用多个catch语句进行处理。
【例7.4】使用初始化参数输入两个数字,并进行除法操作
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P222_124641.jpg?sign=1739258311-KuJxMjLIxy9GKNBi5oXzUipLzE4wR09k-0-8a4bd9f2daaccb4497c42ed1381d7e0b)
此时,因为要使用初始化输入参数,所以在程序的执行时,输入以下的命令:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P222_124642.jpg?sign=1739258311-7AaCUNLnTvnOouXWdMK8gPaFLMB2lTsP-0-a8ed66d6241983eb29d525dda2e63356)
程序运行结果:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P222_124643.jpg?sign=1739258311-NCNSAG9ICVfaYU1aLYNjqZR4oJhd4DPK-0-11d1ea87707cadad30df39b93f33041b)
第1个参数的值是10,第2个参数的值是2,所以两数相除的结果为5。
提示
字符串转整型操作。
在以上的程序中有这样几段代码:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P223_124649.jpg?sign=1739258311-Lawa3IZ90G1Dnik22BatLJ2Vdaitq2N8-0-0d2a21996278bc500d6c2a93e23f757e)
其中Integer.parseInt(字符串),这个方法是将输入的字符串变为int类型的数据,在第6章的包装类章节中已为读者详细介绍过。
上面的程序属于正确的运行,所以没有任何的问题,但是如果此时有以下的几种情况,则运行肯定会出现问题:
(1)没有输入参数或输入的参数不够,程序运行会出现以下的错误提示:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P223_124650.jpg?sign=1739258311-iYHTJlLeQeRokddCcxBMNtRre3Ggu5oG-0-e619eaa955ab26c7df9969b2bec36b96)
(2)运行时参数输入的不是数字,例如,使用以下的命令运行:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P223_124651.jpg?sign=1739258311-IEYdJe9cYoxTTeKl9f29lImV7FuIkodg-0-55a2c8bb8cb0f6b5e346c5bd1f31ba1e)
则程序运行时会出现以下的错误提示:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P223_124652.jpg?sign=1739258311-xuspng5NjryIWqvNntVNZNGeZn3YeeFD-0-1d5253b8c067134a5c46fae1fea1940d)
(3)输入的除数是0,此错误已经被捕捉了。
上面的程序实际上产生了3个比较明显的异常:
①数组超出绑定异常:ArrayIndexOutOfBoundsException。
②数字格式化异常:NumberFormatException。
③算术异常:ArithmeticException。
此时如果要想保持程序的执行正确,就必须同时对3个异常进行处理,所以此时的catch语句应该有3个,以分别处理不同的异常。
【例7.5】捕捉多个异常
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P223_124653.jpg?sign=1739258311-xeVZo7CV8Q3YKoEL5H0cJe8W0zdCQZ3U-0-3baaaa9fac130355159ad895994589f4)
上面的程序中使用了3个catch语句以分别处理3个不同的异常,但是每个程序的异常都使用这种方式处理的话则肯定很麻烦,因为在程序的开发过程中很难知道到底会有多少个异常。要想解决这个问题,首先要从异常的整体结构来分析。
提示
Java的异常处理格式也在不断改变。
在正常的异常处理中,异常处理的语句格式有try…catch和try…catch…finally。
但是随着Java的发展,对于异常的处理语句也可以采用try…finally的形式编写。
可以不写catch语法,但是这样的操作没有多大意义,有兴趣的读者可以自行研究。
7.1.3 异常类的继承结构
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P224_32318.jpg?sign=1739258311-WYc5pByUekCoA2Vh9WDL8khO9DlLaQXH-0-e235d746355adfcef70bae24cd3fea0f)
图7-4 异常结构
在整个Java的异常结构中,实际上有两个最常用的类:Exception、Error,这两个类全都是Throwable的子类,如图7-4所示。
Exception:一般表示的是程序中出现的问题,可以直接使用try…catch处理。
Error:一般指的是JVM错误,程序中无法处理。
一般情况下,开发者都比较习惯于把以上的两者统称为异常。而之前的算术异常、数字格式化异常等都属于Exception的子类。
提示
异常信息的输出方式。
在catch语句输出异常的时候,除了可以直接使用“System.out.println(异常对象)”的方式打印异常信息,有些时候也会直接使用Exception类中的printStackTrace()方法输出异常信息,代码如下所示:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P224_124663.jpg?sign=1739258311-g819XJTvs74tUDnGWfKzpXZfqiPhJFRK-0-34f1376cba97a00ed04d35a4be5ebb28)
使用这种方式打印的异常信息是最全的,所以在本书后面的有些章节也会使用上面的语法形式输出异常信息。
7.1.4 Java的异常处理机制
在整个Java的异常处理中,实际上也是按照面向对象的方式进行处理,处理的步骤如下:
(1)一旦产生异常,则首先会产生一个异常类的实例化对象;
(2)在try语句中对此异常对象进行捕捉;
(3)产生的异常对象与catch语句中的各个异常类型进行匹配,如果匹配成功,则执行catch语句中的代码。
以上过程如图7-5所示。
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P225_32520.jpg?sign=1739258311-3bUjEbiRi6FPltKdpMsqyI0xw7PjPevo-0-06bbf420595da375d7057748764eb3a9)
图7-5 异常的处理步骤
从之前学习过的对象多态性可以清楚地知道,所有的子类实例可以全部使用父类接收,那么就可以利用向上转型的概念,让所有的异常对象都使用Exception接收。
【例7.6】使用Exception处理其他异常
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P225_124668.jpg?sign=1739258311-AZV93sHwihwk2BknOMnWbsjNezvPb6MA-0-a9a158af0f528022a2689493d17a96a0)
上面的程序在最后直接使用Exception进行其他异常的捕获,那么本程序出现的全部异常就都可以处理了。但是要注意,在Java中所有捕获范围小的异常必须放在捕获大的异常之前,否则程序在编译的时候就会出现错误提示。
【例7.7】错误的异常处理
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P225_124669.jpg?sign=1739258311-mxz1cy2YG4RV0cj3fKF1bk044SUA2POV-0-ae14185334fd4b719ad11c15ba69e31a)
程序执行结果:
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P226_124672.jpg?sign=1739258311-29S9IfUrJINAOZ3S3KRly4YiA05uDPoU-0-165ca728a6fecea86b87b02b9b2f59b2)
上面程序的错误提示是“算术异常”,已经被捕捉了,因为Exception捕捉的范围最大,所以以后的全部异常都是不可能处理的,根据这样一个概念,一般在开发时,不管出现任何异常,都可以直接使用Exception进行处理,这样会比较方便。
【例7.8】使用Exception处理异常
![](https://epubservercos.yuewen.com/DE8B7B/15253389304122106/epubprivate/OEBPS/Images/Figure-P226_124673.jpg?sign=1739258311-akrlYAuuOUClcIDrLT0LZWuKoygXqvhw-0-13d1599c7093769286feadbf03efac51)
上面的代码直接使用Exception进行异常的处理,所以任何的异常都可以非常方便地进行处理。
说明
提问:可不可以直接使用Throwable?
既然可以使用Exception方便地捕获所有异常,那么以后在程序中直接使用Throwable的类不是更好吗?
回答:不建议这样使用,最大只能捕获Exception。
首先使用Throwable捕获异常,这在代码中是没有任何的问题,因为Throwable捕获的范围是最大的。但一般开发中是不会直接使用Throwable进行捕获的,对于Throwable来说有Exception、Error两个子类,Error类本身不需要程序处理,而程序中需要处理的只是Exception,所以没必要使用Throwable。
另外,要提醒读者的是,对于一个程序,如果有多个异常最好分别进行捕获,而不要直接使用Exception捕获全部异常。