第十二章 异常处理和文本IO

第十二章 异常处理和文本I/O

12.1引言

异常是运行时错误。异常处理使得程序可以处理运行时错误,并且继续通常的执行。

在程序运行过程中,如果JVM检测出一个不可能执行的操作,就会出现运行时错误(runtime error)。

在Java中,运行时错误会作为异常抛出。异常就是一种对象,表示阻止正常运行的错误或者情况。

12.2 异常处理概述

异常是从方法抛出的。方法的调用者可以捕获以及处理该异常。

下面请看如下例子,该例子表明读取两个整数并显示它们的商。

请添加图片描述

如果输入0赋值给第二个数字,那就会产生一个运行时错误,因为不能用一个整数除以0(注意,一个浮点数除以0是不会产生异常的)。解决这个错误的一个简单方法就是添加一个if语句来测试第二个数字,如下图所示。

请添加图片描述

下面重写上面的代码,使用一个方法计算商,如下图所示。

请添加图片描述

方法 quotient 返回两个整数的商。如果number2为0,则不能返回一个值,因此程序在第7行终止。这显然是一个问题。不应该让方法来终止程序——应该由调用者决定是否终止程序。

方法如何通知它的调用者一个异常产生了呢?Java可以让一个方法可以抛出一个异常该异常可以被调用者捕获和处理。上面所示代码可以重写如下图所示。
请添加图片描述

请添加图片描述

如果number2为0,方法通过执行以下语句抛出一个异常(第6行):

throw new ArithmeticException("Divisor cannot be zero");

在这种情况下,抛出的值为new ArithmeticException("Divisor cannot be zero"),称为一个异常(exception)。throw语句的执行称为抛出一个异常(throwing an exception)。异常就是一个从异常类创建的对象。在这种情况下,异常类就是java.lang.ArithmeticException。构造方法 ArithmeticException(str)被调用以构建一个异常对象,其中str是描述异常的消息。
当异常被抛出时,正常的执行流程被中断。就像它的名字所提示的,“抛出异常”就是将异常从一个地方传递到另一个地方。调用方法的语句包含在一个try块和一个catch块中。try块(第19~23行)包含了正常情况下执行的代码。异常被catch块所捕获。catch块中的代码被执行以处理异常。之后,catch块之后的语句(第29行)被执行。throw语句类似于方法的调用,但不同于调用方法的是,它调用的是catch块。从某种意义上讲,catch块就像带参数的方法定义,这些参数匹配抛出的值的类型。但是,它不像方法,在执行完catch块之后,程序控制不返回到throw语句;而是执行catch块后的下一条语句。

catch 块的头部

catch(ArithmeticException ex)

中的标识符ex的作用很像是方法中的参数。所以,这个参数称为catch块的参数。ex之前的类型(例如,ArithmeticException)指定了catch块可以捕获的异常类型。一旦捕获该异常,就能从catch块体中的参数访问这个抛出的值。总之,一个try-throw-catch块的模板可能如下所示:

try {
	Code to run;
	A statement or a method that may throw an exception;
    More code to run;
}
catch(type ex){
    Code to process the exception;
}

一个异常可能是通过try块中的throw语句直接抛出,或者调用一个可能会抛出异常的方法而抛出。

12.3 异常类型

异常是对象,而对象都采用类来定义。异常的根类是java.lang.Throwable

下图是一些常见的异常类

请添加图片描述

注意:类名ErrorExceptionRuntimeException有时候容易引起混淆。这三种类都是异常,这里讨论的错误都发生在运行时。

Throwable类是所有异常类的根。所有的Java异常类都直接或者间接地继承自Throwable。可以通过继承Exception或者Exception的子类来创建自己的异常类。

这些异常类可以分为三种主要类型:系统错误、异常和运行时异常。

  • 系统错误(system error)是由Java虚拟机抛出的,用Error类表示。Error 类描述的是内部系统错误。这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。下图列出了Error的子类的一些例子。

请添加图片描述

  • 异常(exception)是用Exception类表示的,它描述的是由你的程序和外部环境所引起的错误,这些错误能被程序捕获和处理。下图列出Exception类的子类的一些例子。

    请添加图片描述

  • 运行时异常(runtime exception)是用RuntimeException类表示的,它描述的是程序设计错误,例如,错误的类型转换、访问一个越界数组或数值错误。运行时异常通常表明了编程错误。下图列出RuntimeException的子类的一些例子。

    请添加图片描述

12.4 声明、抛出和捕获异常

异常的处理器是通过从当前的方法开始,沿着方法调用链,按照异常的反向传播方向找到的。

Java的异常处理模型基于三种操作:声明一个异常(declaring an exception)、抛出一个异常(throwing an exception)和捕获一个异常(catching an exception),如下图所示。

请添加图片描述

12.4.1 声明异常

在Java中,当前执行的语句必属于某个方法。Java解释器调用main方法开始执行一个程序。每个方法都必须声明它可能抛出的必检异常的类型。这称为声明异常(declaring exception)。

为了在方法中声明一个异常,就要在方法头中使用关键字throws,如下所示:

public void myMethod() throws IOException

关键字 throws 表明 myMethod 方法可能会抛出异常IOException。如果方法可能会抛出多个异常,就可以在关键字throws后添加一个用逗号分隔的异常列表:

public void myMethod()
	throws Exception1,Exception2,...,ExceptionN

如果父类中的方法没有声明异常,那么就不能在子类中对其重写时声明异常。

12.4.2 抛出并常

检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这称为抛出一个异常(throwing an exception)。这里有一个例子,假如程序发现传递给方法的参数与方法的合约不符(例如,方法中的参数必须是非负的,但是传入的是一个负参数),这个程序就可以创建IllegalArgumentException的一个实例并抛出它,如下所示:

IllegalArgumentException ex = 
	new I11egalArgumentException("Wrong Argument");
throw ex;

或者,根据偏好,也可以使用下面的语句:

throw new IllegalArgumentException("Wrong Argument");

注意:IllegalArgumentExceptionJavaAPI中的一个异常类。通常,Java API中的每个异常类至少有两个构造方法:一个无参构造方法和一个带有可以描述这个异常的String 参数的构造方法。该参数称为异常消息(exception message),它可以通过一个异常对象调用 getMessage() 获取。

提示:声明异常的关键字是throws,抛出异常的关键字是throw

12.4.3 捕获异常

现在我们知道了如何声明一个异常以及如何抛出一个异常。当抛出一个异常时,可以在try-catch块中捕获和处理它,如下所示:

try {
	statements;	//Statements that may throw exceptions
} catch(Exception1 exVar1) {
    handler for exception1;
} catch(Exception2 exVar2) {
    handler for exception2;
}
...
 catch(ExceptionN exVarN) {
    handler for exceptionN;
}

如果在执行try块的过程中没有出现异常,则跳过catch子句。

如果try块中的某条语句抛出一个异常,Java 就会跳过 try 块中剩余的语句,然后开始查找处理这个异常的代码。处理这个异常的代码称为异常处理器(exception handler);可以从当前的方法开始,沿着方法调用链,按照异常的反向传播方向找到这个处理器。从第一个到最后一个逐个检查catch块,判断在catch块中的异常类实例是否是该异常对象的类型。如果是,就将该异常对象赋值给所声明的变量,然后执行catch块中的代码。如果没有发现异常处理器,Java会退出这个方法,把异常传递给这个方法的调用者,继续同样的过程来查找处理器。如果在调用的方法链中找不到处理器,程序就会终止并且在控制台上打印出错信息。查找处理器的过程称为捕获一个异常(catching an exception)。

抛出一个异常,如下图所示。考虑下面的情形:

假设main方法调用method1,method1调用method2,method2 调用 method3,method3抛出一个异常,如下图所示。考虑下面情形:

请添加图片描述

如果异常类型是Exception3,它就会被method2中处理异常ex3catch块捕获。跳过statement5,然后执行statement6

如果异常类型是Exception2,则退出method2,控制被返回给method1,而这个异常就会被method1中处理异常ex2catch块捕获。跳过statement3,然后执行statement4

如果异常类型是Exception1,则退出method1,控制被返回给main方法,而这个异常就会被main方法中处理异常ex1catch块捕获。跳过statement1,然后执行statement2

如果异常类型没有在method2method1main方法中被捕获,程序就会终止。不执行statement1利用statement2

注意:各种异常类可以从一个共同的父类中派生。如果一个catch块可以捕获一个父类的异常对象,它就能捕获那个父类的所有子类的异常对象。

12.4.4 从异常中获取信息

异常对象中包含关于异常的有价值的信息。可以利用下面这些java.lang.Throwable类中的实例方法获取有关异常的信息,如下图所示。printstackTrace()方法在控制台上打印栈的跟踪信息。栈的跟踪列出调用栈中所有的方法,这为调试运行时错误提供了很有用的信息。getstackTrace()方法提供编程的方式,来访问由printstackTrace()打印输出的栈跟踪信息。

请添加图片描述

12.5 finally子句

无论异常是否产生,finally子句总会被执行。

有时候,不论异常是否出现或者是否被捕获,都希望执行某些代码。Java的finally子句可以用来实现这个目的。finally子句的语法如下所示:

try {
	statements;
} catch (TheException ex) {
	handling ex;
}
finally {
	finalStatements;javajvav
}

在任何情况下,finally块中的代码都会执行,不论try块中是否出现异常或者是否被捕获。考虑下面三种可能出现的情况:

  • 如果 try块中没有出现异常,执行finalStatements,然后执行try语句的下一条语句。
  • 如果try块中有一条语句引起了异常并被catch块捕获,会跳过try块的其他语句执行 catch 块和 finally 子句。执行 try 语句后的下一条语句。
  • 如果 try 块中的一条语句引起异常,但是没有被任何catch块捕获,就会跳过 try块中的其他语句,执行finally子句,并且将异常传递给这个方法的调用者。

即使在到达 finally块之前有一个return语句,finally块还是会执行。

注意:使用finally子句时可以略去catch 块。

12.6 何时使用异常

当错误需要被方法的调用者处理的时候,方法应该抛出一个异常。

try块包含正常情况下执行的代码。catch块包含异常情况下执行的代码。异常处理将错误处理代码从正常的编程任务中分离出来,这样,可以使程序更易读、更易修改。但是应该注意,由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,所以,异常处理通常需要更多的时间和资源。

12.7 重新抛出异常

如果异常处理器不能处理一个异常,或者只是简单地希望它的调用者注意到该异常,Java 允许该异常处理器重新抛出异常。

重新抛出异常的语法如下所示:

try {
	statements;
}
catch (TheException ex) {
	perform operations before exits;
	throw ex;
}

语句throw ex重新抛出异常给调用者,以便调用者的其他处理器获得处理异常ex的机会。

12.8 链式异常

与另一个异常一起抛出一个异常,构成了链式异常。

有时候,可能需要同最初异常一起抛出一个新异常(带有附加信息),这称为链式并常(chained exception)。下图展示了如何产生和抛出链式异常。

请添加图片描述

12.9 创建自定义异常类

可以通过继承 java.lang.Exception 类来定义一个自定义异常类。

Java提供相当多的异常类,尽量使用它们而不要创建自己的异常类。然而,如果遇到一个不能用预定义异常类来充分描述的问题,那就可以通过继承Exception类或其子类(例如,IOException)来创建自己的异常类。

12.10 Fie类

File类包含了获得一个文件/目录的属性,以及对文件/目录进行改名和删除的方法。

在学完异常处理后,我们来学习文件处理。存储在程序中的数据是暂时的,当程序终止时它们就会丢失。为了能够永久地保存程序中创建的数据,需要将它们存储到磁盘或其他永久存储设备的文件中。这样,这些文件之后可以被其他程序传输和读取。由于数据存储在文件中,所以本节就介绍如何使用File类获取文件/目录的属性以及删除和重命名文件/目录,以及创建目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值