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

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

不少事件并不是老是按照人们本身设计意愿顺利发展的,而是有可以出现这样那样的异常状况。例如:你计划周末郊游,你的计划会安排满满的,你计划多是这样的:从家里出发→到达目的→游泳→烧烤→回家。但天有不测风云,当前你准备烧烤时候天降大雨,你只能终止郊游提早回家。“天降大雨”是一种异常状况,你的计划应该考虑到这样状况,而且应该有处理这种异常的预案。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 zero

at com.a51work6.HelloWorld.main(HelloWorld.java:9)

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

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

异常类继承层次

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

Throwable类 {#throwable}

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

  • 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 zero

toString() : java.lang.ArithmeticException: / by zero

printStackTrace()输出信息以下:

java.lang.ArithmeticException: / by zero

at 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",则会打印信息。

处理ParseException

java.text.ParseException: Unparseable date: "201A-18-18"

日期 = null

at 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. 进入图灵社区电子书
相关文章
相关标签/搜索