《Java从小白到大牛》之第14章 异常处理(下)

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

释放资源

有时在try-catch语句中会占用一些非Java资源,如:打开文件、网络连接、打开数据库连接和使用数据结果集等,这些资源并非Java资源,不能通过JVM的垃圾收集器回收,需要程序员释放。为了确保这些资源能够被释放可以使用finally代码块或Java 7之后提供自动资源管理(Automatic Resource Management)技术。

finally代码块 {#finally}

try-catch语句后面还可以跟有一个finally代码块,try-catch-finally语句语法如下:

try{

//可能会生成异常语句

} catch(Throwable e1){

//处理异常e1

} catch(Throwable e2){

//处理异常e1

} catch(Throwable eN){

//处理异常eN

} finally{

//释放资源

}

无论try正常结束还是catch异常结束都会执行finally代码块,如同14-2所示。

使用finally代码块示例代码如下:

//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();

} finally { ①

try {

if (readfile != null) {

readfile.close(); ②

}

} catch (IOException e) {

e.printStackTrace();

}

try {

if (ir != null) {

ir.close(); ③

}

} catch (IOException e) {

e.printStackTrace();

}

try {

if (in != null) {

in.close(); ④

}

} catch (IOException e) {

e.printStackTrace();

}

} ⑤

return null;

}

}

上述代码第①行~第⑤行是finally语句,在这里通过关闭流释放资源,FileInputStream、InputStreamReader和BufferedReader是三个输入流,它们都需要关闭,见代码第②行~第④行通过流的close()关闭流,但是流的close()方法还有可以能发生IOException异常,所以这里又针对每一个close()语句还需要进行捕获处理。

注意 为了代码简洁等目的,可能有的人会将finally代码中的多个嵌套的try-catch语句合并,例如将上述代码改成如下形式,将三个有可以发生异常的close()方法放到一个try-catch。读者自己考虑一下这处理是否稳妥呢?每一个close()方法对应关闭一个资源,如果第一个close()方法关闭时发生了异常,那么后面的两个也不会关闭,因此如下的程序代码是有缺陷的。


try {

... ...

} catch (FileNotFoundException e) {

... ...

} catch (IOException e) {

... ...

} catch (ParseException e) {

... ...

} finally {

try {

if (readfile != null) {

readfile.close();

}

if (ir != null) {

ir.close();

}

if (in != null) {

in.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

### 自动资源管理 {#-0}

14.4.1节使用finally代码块释放资源会导致程序代码大量增加,一个finally代码块往往比正常执行的程序还要多。在Java 7之后提供自动资源管理(Automatic Resource Management)技术,可以替代finally代码块,优化代码结构,提高程序可读性。

自动资源管理是在try语句上的扩展,语法如下:
```java
try (声明或初始化资源语句) {

//可能会生成异常语句

} catch(Throwable e1){

//处理异常e1

} catch(Throwable e2){

//处理异常e1

} catch(Throwable eN){

//处理异常eN

}

在try语句后面添加一对小括号“()”,其中是声明或初始化资源语句,可以有多条语句语句之间用分号“;”分隔。

示例代码如下:

//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() {

// 自动资源管理

try (FileInputStream readfile = new FileInputStream("readme.txt"); ①

InputStreamReader ir = new InputStreamReader(readfile); ②

BufferedReader 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;

}

}

上述代码第①行~第③行是声明或初始化三个输入流,三条语句放到在try语句后面小括号中,语句之间用分号“;”分隔,这就是自动资源管理技术了,采用了自动资源管理后不再需要finally代码块,不需要自己close这些资源,释放过程交给了JVM。

注意 所有可以自动管理的资源需要实现AutoCloseable接口,上述代码中三个输入流FileInputStream、InputStreamReader和BufferedReader从Java 7之后实现AutoCloseable接口,具体哪些资源实现AutoCloseable接口需要查询API文档。

throws与声明方法抛出异常 {#throws}

在一个方法中如果能够处理异常,则需要捕获并处理。但是本方法没有能力处理该异常,捕获它没有任何意义,则需要在方法后面声明抛出该异常,通知上层调用者该方法有可以发生异常。

方法后面声明抛出使用throws关键字,回顾一下10.3.3节成员方法语法格式如下:

class className {

[public | protected | private ] [static] [final | abstract] [native] [synchronized]

type methodName([paramList]) [throws exceptionList] {

//方法体

}

}

其中参数列表之后的[throws exceptionList]语句是声明抛出异常。方法中可能抛出的异常(除了Error和RuntimeException及其子类外)都必须通过throws语句列出,多个异常之间采用逗号(,)分隔。

注意 如果声明抛出的多个异常类之间有父子关系,可以只声明抛出父类。但如果没有父子关系情况下,最好明确声明抛出每一个异常,因为上层调用者会根据这些异常信息进行相应的处理。假如一个方法中有可能抛出IOException和ParseException两个异常,那么声明抛出IOException和ParseException呢?还是只声明抛出Exception呢?因为Exception是IOException和ParseException的父类,只声明抛出Exception从语法是允许的,但是声明抛出IOException和ParseException更好一些。

如果将14.3节示例进行修改,在readDate()方法后声明抛出异常,代码如下:

//HelloWorld.java文件

package com.a51work6;

… …

public class HelloWorld {

public static void main(String[] args) { ①

try {

Date date = readDate(); ②

System.out.println("读取的日期 = " + date);

} catch (IOException e) { ③

System.out.println("处理IOException...");

e.printStackTrace();

} catch (ParseException e) { ④

System.out.println("处理ParseException...");

e.printStackTrace();

}

}

public static Date readDate() throws IOException, ParseException { ⑤

// 自动资源管理

FileInputStream readfile = new FileInputStream("readme.txt"); ⑥

InputStreamReader ir = new InputStreamReader(readfile);

BufferedReader 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;

}

}

由于readDate()方法中代码第⑥、⑦、⑧行都有可能引发异常。在readDate()方法内又没有捕获处理,所有需要在代码第⑤行方法后声明抛出异常,事实上有三个异常FileNotFoundException、IOException和ParseException,由于FileNotFoundException属于IOException异常,所以只声明IOException和ParseException就可以了。

一旦readDate()方法声明抛出了异常,那么它的调用者main()方法,也会面临同样的问题:要么捕获自己处理,要么抛出给上层调用者。如果一旦发生异常main()方法也选择抛出那么程序运行就会终止。本例中main()方法是捕获异常进行处理,捕获异常过程前面已经介绍过了,这里不再赘述。

自定义异常类

有些公司为了提高代码的可重用性,自己开发了一些Java类库或框架,其中少不了自己编写了一些异常类。实现自定义异常类需要继承Exception类或其子类,如果自定义运行时异常类需继承RuntimeException类或其子类。

实现自定义异常类示例代码如下:

package com.a51work6;

public class MyException extends Exception { ①

public MyException() { ②

}

public MyException(String message) { ③

super(message);

}

}

上述代码实现了自定义异常,自定义异常类一般需要提供两个构造方法,一个是代码第②行的无参数的默认构造方法,异常描述信息是空的;另一个是代码第③行的字符串参数的构造方法,message是异常描述信息,getMessage()方法可以获得这些信息。

自定义异常就这样简单,主要是提供两个构造方法就可以了,

throw与显式抛出异常 {#throw}

Java异常相关的关键字中有两个非常相似,它们是throws和throw,其中throws关键字前面14.5节已经介绍了,throws用于方法后声明抛出异常,而throw关键字用来人工引发异常。本节之前读者接触到的异常都是由于系统生成的,当异常发生时,系统一个异常对象,并将其抛出。但也可以通过throw语句显式抛出异常,语法格式如下:

throw Throwable或其子类的实例

所有Throwable或其子类的实例都可以通过throw语句抛出。

显式抛出异常目的有很多,例如不想某些异常传给上层调用者,可以捕获之后重新显式抛出另外一种异常给调用者。

修改14.4节示例代码如下:

//HelloWorld.java文件

package com.a51work6;

… …

public class HelloWorld {

public static void main(String[] args) {

try {

Date date = readDate();

System.out.println("读取的日期 = " + date);

} catch (MyException e) {

System.out.println("处理MyException...");

e.printStackTrace();

}

}

public static Date readDate() throws MyException {

// 自动资源管理

try (FileInputStream readfile = new FileInputStream("readme.txt");

InputStreamReader ir = new InputStreamReader(readfile);

BufferedReader 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) { ①

throw new MyException(e.getMessage()); ②

} catch (IOException e) { ③

throw new MyException(e.getMessage()); ④

} catch (ParseException e) {

System.out.println("处理ParseException...");

e.printStackTrace();

}

return null;

}

}

如果软件设计者不希望readDate()方法中捕获的FileNotFoundException和IOException异常出现在main()方法(上层调用者)中,那么可以在捕获到FileNotFoundException和IOException异常时,通过throw语句显式抛出一个异常,见代码第②行和第④行throw new MyException(e.getMessage())语句,MyException是自定义的异常。

注意 throw显式抛出的异常与系统生成并抛出的异常,在处理方式上没有区别,就是两种方法:要么捕获自己处理,要么抛出给上层调用者。在本例中是声明抛出,所以在readDate()方法后面要声明抛出MyException异常。

本章小结

本章介绍了Java异常处理机制,其中包括Java异常类继承层次、捕获异常、释放资源、throws、throw和自定义异常类。读者需要重点掌握捕获异常处理,熟悉throws和throw的区分和用法。

配套视频

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

配套源代码

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

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

  1. 进入百度阅读电子书
  2. 进入图灵社区电子书

原文地址:http://blog.51cto.com/tonyguan/2299896

时间: 2024-11-07 07:23:17

《Java从小白到大牛》之第14章 异常处理(下)的相关文章

《Java从小白到大牛精简版》——第1章 开篇综述

Java诞生到现在已经有20多年了,但是Java仍然是非常热门的编程语言之一,很多平台中使用Java开发.表1-1所示的是TIOBE社区发布的2016年5月和2017年5月的编程语言排行榜,可见Java语言的热度,或许这也是很多人选择学习Java的主要原因. 表 1-1 TIOBE编程语言排行榜 2017年5月 2016年5月 变化 编程语言 评级 评级变化 1 1 Java 14.639% -6.320% 2 2 C 7.002% -6.220% 3 3 C++ 4.751% -1.950%

《Java从小白到大牛精简版》——前言

内容简介 本书是一本Java语言学习教程,读者群是零基础小白,通过本书的学习能够成为Java大牛.主要内容包括:Java语法基础.数据类型.运算符.控制语句.数组.字符串.面向对象基础.继承与多态.抽象类与接口.集合框架.异常处理.输入输出和网络编程等技术. 版权声明 <Java从小白到大牛精简版>免费电子图书是作者关东升原创作品,作者已将该书(包括:文字.图片和源代码)进行了版权注册,版权归作者关东升所有,仅供个人研究和学习之用.任何单位或个人不得以任何方式进行出版.篡改.编辑,任何单位或个

《Java从小白到大牛》之第7章 控制语句

<Java从小白到大牛>纸质版已经上架了!!! 程序设计中的控制语句有三种,即顺序.分支和循环语句.Java程序通过控制语句来管理程序流,完成一定的任务.程序流是由若干个语句组成的,语句可以是一条单一的语句,也可以是一个用大括号({})括起来的复合语句.Java中的控制语句有以下几类: 分支语句:if和switch. 循环语句:while.do-while和for 跳转语句:break.continue.return和throw 7.1 分支语句 分支语句提供了一种控制机制,使得程序具有了&q

《Java从小白到大牛》之第10章 面向对象基础(下)

<Java从小白到大牛>纸质版已经上架了!!! 封装性与访问控制 Java面向对象的封装性是通过对成员变量和方法进行访问控制实现的,访问控制分为4个等级:私有.默认.保护和公有,具体规则如表10-1所示. 表 101 Java类成员的访问控制 可否直接访问控制等级 同一个类 同一个包 不同包的子类 不同包非子类 私有 Yes 默认 Yes Yes 保护 Yes Yes Yes 公有 Yes Yes Yes Yes 下面详细解释一下这4种访问级别. 私有级别 {#-0} 私有级别的关键字是pri

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

<Java从小白到大牛>纸质版已经上架了!!! 类实例化可生成对象,实例方法就是对象方法,实例变量就是对象属性.一个对象的生命周期包括三个阶段:创建.使用和销毁.前面章节已经多少用到了对象,这一章详细介绍一下对象的创建和销毁等相关知识. 创建对象 创建对象包括两个步骤:声明和实例化. 1. 声明 声明对象与声明普通变量没有区别,语法格式如下: type objectName; 其中type是引用类型,即类.接口和数组.示例代码如下: String name; 该语句声明了字符串类型对象name

《Java从小白到大牛》之第12章 继承与多态

<Java从小白到大牛>纸质版已经上架了!!! 类的继承性是面向对象语言的基本特性,多态性前提是继承性.Java支持继承性和多态性.这一章讨论Java继承性和多态性. Java中的继承 {#java} 为了了解继承性,先看这样一个场景:一位面向对象的程序员小赵,在编程过程中需要描述和处理个人信息,于是定义了类Person,如下所示: //Person.java文件 package com.a51work6; import java.util.Date; public class Person

《Python从小白到大牛》第1章 开篇综述

Python诞生到现在已经有20多年了,但是Python仍然是非常热门的编程语言之一,很多平台中使用Python开发.表1-1所示的是TIOBE社区发布的2017年3月和2018年3月的编程语言排行榜,可见Python语言的热度,或许这也是很多人选择学习Python的主要原因. Python语言历史 Python之父荷兰人吉多 范·罗苏姆(Guido vanRossum)在1989年圣诞节期间,在阿姆斯特丹,为了打发圣诞节的无聊时间,决心开发一门解释程序语言.1991年第一个Python解释器公

《Python从小白到大牛》第6章 数据类型

在声明变量时会用到数据类型,在前面已经用到一些数据类型,例如整数和字符串等.在Python中所有的数据类型都是类,每一个变量都是类的"实例".没有基本数据类型的概念,所以整数.浮点和字符串也都是类. Python有6种标准数据类型:数字.字符串.列表.元组.集合和字典,列表.元组.集合和字典可以保存多项数据,他们每一个都是一种数据结构,本书中把他们统称为"数据结构"类型. 本章先介绍数字和字符串,列表.元组.集合和字典数据类型后面章节会详细介绍. 数字类型 Pyth

《Python从小白到大牛》第8章 控制语句

程序设计中的控制语句有三种,即顺序.分支和循环语句.Python程序通过控制语句来管理程序流,完成一定的任务.程序流是由若干个语句组成的,语句可以是一条单一的语句,也可以是复合语句.Python中的控制语句有以下几类: 分支语句:if 循环语句:while和for 跳转语句:break.continue和return 分支语句 分支语句提供了一种控制机制,使得程序具有了"判断能力",能够像人类的大脑一样分析问题.分支语句又称条件语句,条件语句使部分程序可根据某些表达式的值被有选择地执行