《Java从小白到大牛》纸质版已经上架了!!!

很多事件并非总是按照人们自己设计意愿顺利发展的,而是有能够出现这样那样的异常情况。例如:你计划周末郊游,你的计划会安排满满的,你计划可能是这样的:从家里出发→到达目的→游泳→烧烤→回家。但天有不测风云,当前你准备烧烤时候天降大雨,你只能终止郊游提前回家。“天降大雨”是一种异常情况,你的计划应该考虑到这样情况,并且应该有处理这种异常的预案。

为增强程序的健壮性,计算机程序的编写也需要考虑处理这些异常情况,Java语言提供了异常处理功能,本章介绍Java异常处理机制。

从一个问题开始

为了学习Java异常处理机制,首先看看下面程序。

//HelloWorld.java文件package com.a51work6;public class HelloWorld {public static void main(String[] args) {int a = 0;System.out.println(5 / a);}}

这个程序没有编译错误,但会发生如下的运行时错误:

Exception in thread "main" java.lang.ArithmeticException: / by zeroat com.a51work6.HelloWorld.main(HelloWorld.java:9)

在数学上除数不能为0,所以程序运行时表达式(5 / a)会抛出ArithmeticException异常,ArithmeticException是数学计算异常,凡是发生数学计算错误都会抛出该异常。

程序运行过程中难免会发生异常,发生异常并不可怕,程序员应该考虑到有可能发生这些异常,编程时应该捕获并进行处理异常,不能让程序发生终止,这就是健壮的程序。

异常类继承层次

异常封装成为类Exception,此外,还有Throwable和Error类,异常类继承层次如图14-1所示。

Throwable类 {#throwable}

从图14-1可见,所有的异常类都直接或间接地继承于java.lang.Throwable类,在Throwable类有几个非常重要的方法:

  • String getMessage():获得发生异常的详细消息。
  • void printStackTrace():打印异常堆栈跟踪信息。
  • String toString():获得异常对象的描述。

提示 堆栈跟踪是方法调用过程的轨迹,它包含了程序执行过程中方法调用的顺序和所在源代码行号。

为了介绍Throwable类的使用,下面修改14.1节的示例代码如下:

//HelloWorld.java文件package com.a51work6;public class HelloWorld {public static void main(String[] args) {int a = 0;int result = divide(5, a);System.out.printf("divide(%d, %d) = %d", 5, a, result);}public static int divide(int number, int divisor) {try {return number / divisor;} catch (Throwable throwable) { ①System.out.println("getMessage() : " + throwable.getMessage()); ②System.out.println("toString() : " + throwable.toString()); ③System.out.println("printStackTrace()输出信息如下:");throwable.printStackTrace(); ④}return 0;}}

运行结果如下:

getMessage() : / by zerotoString() : java.lang.ArithmeticException: / by zeroprintStackTrace()输出信息如下:java.lang.ArithmeticException: / by zeroat com.a51work6.HelloWorld.divide(HelloWorld.java:17)at com.a51work6.HelloWorld.main(HelloWorld.java:10)divide(5, 0) = 0

将可以发生异常的语句System.out.println(5 / a)放到try-catch代码块中,称为捕获异常,有关捕获异常的相关知识会在下一节详细介绍。在catch中有一个Throwable对象throwable,throwable对象是系统在程序发生异常时创建,通过throwable对象可以调用Throwable中定义的方法。

代码第②行是调用getMessage()方法获得异常消息,输出结果是“/ by zero”。代码第③行是调用toString()方法获得异常对象的描述,输出结果是java.lang.ArithmeticException: / by zero。代码第④行是调用printStackTrace()方法打印异常堆栈跟踪信息。

提示 堆栈跟踪信息从下往上,是方法调用的顺序。首先JVM调用是com.a51work6.HelloWorld类的main方法,接着在HelloWorld.java源代码第10行调用com.a51work6.HelloWorld类的divide方法,在HelloWorld.java源代码第17行发生了异常,最后输出的是异常信息。

Error和Exception {#error-exception}

从图14-1可见,Throwable有两个直接子类:Error和Exception。

  1. Error

Error是程序无法恢复的严重错误,程序员根本无能为力,只能让程序终止。例如:JVM内部错误、内存溢出和资源耗尽等严重情况。

  1. Exception

Exception是程序可以恢复的异常,它是程序员所能掌控的。例如:除零异常、空指针访问、网络连接中断和读取不存在的文件等。本章所讨论的异常处理就是对Exception及其子类的异常处理。

受检查异常和运行时异常 {#-0}

从图14-1可见,Exception类可以分为:受检查异常和运行时异常。

  1. 受检查异常

如图14-1所示,受检查异常是除RuntimeException以外的异常类。它们的共同特点是:编译器会检查这类异常是否进行了处理,即要么捕获(try-catch语句),要么不抛出(通过在方法后声明throws),否则会发生编译错误。它们种类很多,前面遇到过的日期解析异常ParseException。

  1. 运行时异常

运行时异常是继承RuntimeException类的直接或间接子类。运行时异常往往是程序员所犯错误导致的,健壮的程序不应该发生运行时异常。它们的共同特点是:编译器不检查这类异常是否进行了处理,也就是对于这类异常不捕获也不抛出,程序也可以编译通过。由于没有进行异常处理,一旦运行时异常发生就会导致程序的终止,这是用户不希望看到的。由于14.2.1节除零示例的ArithmeticException异常属于RuntimeException异常,见图14-1所示,可以不用加try-catch语句捕获异常。

提示 对于运行时异常通常不采用抛出或捕获处理方式,而是应该提前预判,防止这种发生异常,做到未雨绸缪。例如14.2.1节除零示例,在进行除法运算之前应该判断除数是非零的,修改示例代码如下,从代码可见提前预判这样处理要比通过try-catch捕获异常要友好的多。


//HelloWorld.java文件

package com.a51work6;

public class HelloWorld {

public static void main(String[] args) {

int a = 0;

int result = divide(5, a);

System.out.printf("divide(%d, %d) = %d", 5, a, result);

}

public static int divide(int number, int divisor) {

//判断除数divisor非零,防止运行时异常

if (divisor != 0) {

return number / divisor;

}

return 0;

}

}

除了图14-1所示异常,还有很多异常,本书不能一一穷尽,随着学习的深入会介绍一些常用的异常,其他异常读者可以自己查询API文档。
## 捕获异常在学习本内容之前,你先考虑一下,在现实生活中是如何对待领导交给你的任务呢?当然无非是两种:自己有能解决的自己处理;自己无力解决的反馈给领导,让领导自己处理。那么对待受检查异常亦是如此。当前方法有能力解决,则捕获异常进行处理;没有能力解决,则抛出给上层调用方法处理。如果上层调用方法还无力解决,则继续抛给它的上层调用方法,异常就是这样向上传递直到有方法处理它,如果所有的方法都没有处理该异常,那么JVM会终止程序运行。这一节先介绍一下捕获异常。### try-catch语句 {#try-catch}捕获异常是通过try-catch语句实现的,最基本try-catch语句语法如下:
```java
try{//可能会发生异常的语句} catch(Throwable e){//处理异常e}
  1. try代码块

try代码块中应该包含执行过程中可能会发生异常的语句。一条语句是否有可能发生异常,这要看语句中调用的方法。例如日期格式化类DateFormat的日期解析方法parse(),该方法的完整定义如下:

public Date parse(String source) throws ParseException

方法后面的throws ParseException说明:当调用parse()方法时有可以能产生ParseException异常。

提示 静态方法、实例方法和构造方法都可以声明抛出异常,凡是抛出异常的方法都可以通过try-catch进行捕获,当然运行时异常可以不捕获。一个方法声明抛出什么样的异常需要查询API文档。

  1. catch代码块

每个try代码块可以伴随一个或多个catch代码块,用于处理try代码块中所可能发生的多种异常。catch(Throwable e)语句中的e是捕获异常对象,e必须是Throwable的子类,异常对象e的作用域在该catch代码块中。

下面看看一个try-catch示例:

//HelloWorld.java文件package com.a51work6;import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class HelloWorld {public static void main(String[] args) {Date date = readDate();System.out.println("日期 = " + date);}// 解析日期public static Date readDate() { ①try {String str = "2018-8-18"; //"201A-18-18"DateFormat df = new SimpleDateFormat("yyyy-MM-dd");// 从字符串中解析日期Date date = df.parse(str); ②return date;} catch (ParseException e) { ③System.out.println("处理ParseException…");e.printStackTrace(); ④}return null;}}

上述代码第①行定义了一个静态方法用来将字符串解析成日期,但并非所有的字符串都是有效的日期字符串,因此调用代码第②行的解析方法parse()有可能发生ParseException异常,ParseException是受检查异常,在本例中使用try-catch捕获。代码第③行的e就是ParseException对象。代码第④行e.printStackTrace()是打印异常堆栈跟踪信息,本例中的"2018-8-18"字符串是有个有效的日期字符串,因此不会发生异常。如果将字符串改为无效的日期字符串,如"201A-18-18",则会打印信息。

处理ParseExceptionjava.text.ParseException: Unparseable date: "201A-18-18"日期 = nullat java.text.DateFormat.parse(Unknown Source)at com.a51work6.HelloWorld.readDate(HelloWorld.java:24)at com.a51work6.HelloWorld.main(HelloWorld.java:13)

提示 在捕获到异常之后,通过e.printStackTrace()语句打印异常堆栈跟踪信息,往往只是用于调试,给程序员提示信息。堆栈跟踪信息对最终用户是没有意义的,本例中如果出现异常很有可能是用户输入的日期无效,捕获到异常之后给用户弹出一个对话框,提示用户输入日期无效,请用户重新输入,用户重新输入后再重新调用上述方法。这才是捕获异常之后的正确处理方案。

多catch代码块 {#catch}

如果try代码块中有很多语句会发生异常,而且发生的异常种类又很多。那么可以在try后面跟有多个catch代码块。多catch代码块语法如下:

try{//可能会发生异常的语句} catch(Throwable e){//处理异常e} catch(Throwable e){//处理异常e} catch(Throwable e){//处理异常e}

在多个catch代码情况下,当一个catch代码块捕获到一个异常时,其他的catch代码块就不再进行匹配。

注意 当捕获的多个异常类之间存在父子关系时,捕获异常顺序与catch代码块的顺序有关。一般先捕获子类,后捕获父类,否则子类捕获不到。

示例代码如下:

//HelloWorld.java文件package com.a51work6;……public class HelloWorld {public static void main(String[] args) {Date date = readDate();System.out.println("读取的日期 = " + date);}public static Date readDate() {FileInputStream readfile = null;InputStreamReader ir = null;BufferedReader in = null;try {readfile = new FileInputStream("readme.txt"); ①ir = new InputStreamReader(readfile);in = new BufferedReader(ir);// 读取文件中的一行数据String str = in.readLine(); ②if (str == null) {return null;}DateFormat df = new SimpleDateFormat("yyyy-MM-dd");Date date = df.parse(str); ③return date;} catch (FileNotFoundException e) { ④System.out.println("处理FileNotFoundException...");e.printStackTrace();} catch (IOException e) { ⑤System.out.println("处理IOException...");e.printStackTrace();} catch (ParseException e) { ⑥System.out.println("处理ParseException...");e.printStackTrace();}return null;}}

上述代码通过Java I/O(输入输出)流技术从文件readme.txt中读取字符串,然后解析成为日期。由于Java I/O技术还没有介绍,读者先不要关注I/O技术细节,这考虑调用它们的方法会发生异常就可以了。

在try代码块中第①行代码调用FileInputStream构造方法可以会发生FileNotFoundException异常。第②行代码调用BufferedReader输入流的readLine()方法可以会发生IOException异常。从图14-1可见FileNotFoundException异常是IOException异常的子类,应该先FileNotFoundException捕获,见代码第④行;后捕获IOException,见代码第⑤行。

如果将FileNotFoundException和IOException捕获顺序调换,代码如下:

try{//可能会发生异常的语句} catch (IOException e) {// IOException异常处理} catch (FileNotFoundException e) {// FileNotFoundException异常处理}

那么第二个catch代码块永远不会进入,FileNotFoundException异常处理永远不会执行。

由于上述代码第⑥行ParseException异常与IOException和FileNotFoundException异常没有父子关系,捕获ParseException异常位置可以随意放置。

try-catch语句嵌套 {#try-catch-0}

Java提供的try-catch语句嵌套是可以任意嵌套,修改14.3.2节示例代码如下:

//HelloWorld.java文件package com.a51work6;… …public class HelloWorld {public static void main(String[] args) {Date date = readDate();System.out.println("读取的日期 = " + date);}public static Date readDate() {FileInputStream readfile = null;InputStreamReader ir = null;BufferedReader in = null;try {readfile = new FileInputStream("readme.txt");ir = new InputStreamReader(readfile);in = new BufferedReader(ir);try { ①String str = in.readLine(); ②if (str == null) {return null;}DateFormat df = new SimpleDateFormat("yyyy-MM-dd");Date date = df.parse(str); ③return date;} catch (ParseException e) {System.out.println("处理ParseException...");e.printStackTrace();} ④} catch (FileNotFoundException e) { ⑤System.out.println("处理FileNotFoundException...");e.printStackTrace();} catch (IOException e) { ⑥System.out.println("处理IOException...");e.printStackTrace();}return null;}}

上述代码第①~④行是捕获ParseException异常try-catch语句,可见这个try-catch语句就是嵌套在捕获IOException和FileNotFoundException异常的try-catch语句中。

程序执行时内层如果会发生异常,首先由内层catch进行捕获,如果捕获不到,则由外层catch捕获。例如:代码第②行的readLine()方法可能发生IOException异常,该异常无法被内层catch捕获,最后被代码第⑥行的外层catch捕获。

注意 try-catch不仅可以嵌套在try代码块中,还可以嵌套在catch代码块或finally代码块,finally代码块后面会详细介绍。try-catch嵌套会使程序流程变的复杂,如果能用多catch捕获的异常,尽量不要使用try-catch嵌套。特别对于初学者不要简单地使用Eclipse的语法提示不加区分地添加try-catch嵌套,要梳理好程序的流程再考虑try-catch嵌套的必要性。

多重捕获 {#-0}

多catch代码块客观上提高了程序的健壮性,但是程序代码量大大增加。如果有些异常虽然种类不同,但捕获之后的处理是相同的,看如下代码。

try{//可能会发生异常的语句} catch (FileNotFoundException e) {//调用方法methodA处理} catch (IOException e) {//调用方法methodA处理} catch (ParseException e) {//调用方法methodA处理}

三个不同类型的异常,要求捕获之后的处理都是调用methodA方法。是否可以把这些异常合并处理,Java 7推出了多重捕获(multi-catch)技术,可以帮助解决此类问题,上述代码修改如下:

try{//可能会发生异常的语句} catch (IOException | ParseException e) {//调用方法methodA处理}

在catch中多重捕获异常用“|”运算符连接起来。

注意 有的读者会问什么不写成FileNotFoundException | IOException | ParseException 呢?这是因为由于FileNotFoundException属于IOException异常,IOException异常可以捕获它的所有子类异常了。

配套视频

http://edu.51cto.com/topic/1246.html

配套源代码

http://www.zhijieketang.com/group/5

与本书免费版对应的还有一个收费版本:

  1. 进入百度阅读电子书

  2. 进入图灵社区电子书

《Java从小白到大牛》之第14章 异常处理(上)相关推荐

  1. Educoder- 《JAVA从小白到大牛》(第二章)2-3 Java面向对象 - 封装、继承和多态的综合练习

    提示:本文章为个人学习记录,仅供参考学习,禁止转载,支持交流与讨论. 文章目录 第1关:通关任务一 任务描述 相关知识 面向对象思想 封装 继承 `super()`和`this()` 编程要求 测试说 ...

  2. 《Java从小白到大牛》第29章:项目实战1:开发PetStore宠物商店项目

    第29章 项目实战1:开发PetStore宠物商店项目 前面学习的Java知识只有通过项目贯穿起来,才能将书本中知识变成自己的.通过项目实战读者能够了解软件开发流程,了解所学知识在实际项目中使用的情况 ...

  3. 《Java从小白到大牛》之第9章 字符串

    <Java从小白到大牛>纸质版已经上架了!!! 由字符组成的一串字符序列,称为"字符串",在前面的章节中也多次用到了字符串,本章将重点介绍. Java中的字符串 Jav ...

  4. Java从小白到大牛第1篇 Java基础-关东升-专题视频课程

    Java从小白到大牛第1篇 Java基础-3042人已学习 课程介绍         本视频是智捷课堂推出的一套"Java语言学习立体教程"的视频第一部分,读者以及观看群是初级小白 ...

  5. Java从小白到大牛第2篇 【面向对象】-关东升-专题视频课程

    Java从小白到大牛第2篇 [面向对象]-2739人已学习 课程介绍         本视频是智捷课堂推出的一套"Java语言学习立体教程"的视频第二部分,读者以及观看群是初级小白 ...

  6. Java从小白到大牛第4篇项目实战1——PetStore宠物商店-关东升-专题视频课程

    Java从小白到大牛第4篇项目实战1--PetStore宠物商店-1764人已学习 课程介绍         PetStore是Sun(现在Oracle)公司为了演示自己的Java EE技术,而编写的 ...

  7. 《Java从小白到大牛》之第11章 对象

    <Java从小白到大牛>纸质版已经上架了!!! 类实例化可生成对象,实例方法就是对象方法,实例变量就是对象属性.一个对象的生命周期包括三个阶段:创建.使用和销毁.前面章节已经多少用到了对象 ...

  8. java下标运算符_《Java从小白到大牛精简版》之第6章 运算符(下)

    <Java从小白到大牛>纸质版已经上架了!! 6.4 位运算符 位运算是以二进位(bit)为单位进行运算的,操作数和结果都是整型数据.位运算符有如下几个运算符:&.|.^.~.&g ...

  9. Java从小白到大牛第4篇项目实战视频课程2——Java版QQ-关东升-专题视频课程

    Java从小白到大牛第4篇项目实战视频课程2--Java版QQ-1778人已学习 课程介绍         本项目是Java SE技术实现的QQ2006聊天工具,所涉及到的知识点:Java面向对象.L ...

最新文章

  1. 基于 TensorFlow 在手机端实现文档检测
  2. ASP.NET Core WebApi 返回统一格式参数
  3. UI控件(UIToolbar)
  4. 底层实现_Redis有序集合zset的底层实现
  5. ubuntu安装/查看已安装包的方法
  6. visual studio可以开发app吗_个人能开发App软件吗?从想法到App开发完成,我只用了三天...
  7. NSubstitute完全手册(一)入门基础
  8. rmi远程代码执行漏洞_【最新漏洞简讯】WebLogic远程代码执行漏洞 (CVE202014645)
  9. 实验16 编写包含多个功能子程序的中断例程
  10. 汇编语言程序开发过程
  11. ztree异步加载数据amp;amp;amp;amp;amp;amp;amp;amp;给父节点动态追加子节点
  12. 2019全国知识图谱与语义计算大会
  13. 电脑一键装机软件哪个好 目前干净的一键重装系统软件推荐
  14. 高通手机 进入 高通9008模式
  15. Visual Studio 2022自定义(透明)主题和壁纸完整版
  16. 手机上照片大小怎么改?如何用手机修改图片尺寸?
  17. 5G标准——3GPP TS 38.401
  18. 8.21 binary search
  19. 加拿大11年级计算机课程代码,加拿大读11年级,我经历的选课、学英语、拿高分...
  20. C语言程序——求学生总成绩和平均成绩

热门文章

  1. 在阿里云上创建一个个人网盘(owncloud)
  2. 记一次Linux系统内存占用较高得排查
  3. webApp移动开发之REM
  4. java中常用的字符串的截取方法
  5. python 教程 第十三章、 特殊的方法
  6. 将二叉树中每一层的节点串成链表
  7. ECSHOP头部调用会员的消费积分
  8. LeetCode--258--各位相加*
  9. 看完让你彻底搞懂Websocket原理
  10. DNS隧道工具dns2tcp