Java之路:异常的捕获与处理

不管使用的是哪种语言进行程序设计,都会产生各种各样的错误。Java提供有强大的异常处理机制。在Java中,所有的异常被封装到一个类中,程序出错时会将异常抛出。

一个程序能在正常情况正确地运行,这是一个程序的基本要求。但一个健壮的程序则需要考虑很多会使程序失效的因素,即它要在非正常的情况下,也要能进行必要的处理。程序是由程序员编写的,而程序员是存在思维盲点的,一个合格的程序员能保证Java程序不会出现编译错误,但却无法 “考虑完备”,确保程序在运行时一定不会发生错误,而这些运行时的错误,对Java而言是一种异常。有了异常,就应有相应的手段来处理这些异常,这样才能确保这些异常不会导致丢失数据或破坏系统运行等灾难性后果。

1、异常的基本概念

异常(Exception)也称为例外,指的是所有可能造成计算机无法正常处理的情况,如果没有实现妥善的安排,严重的话可以使得计算机死机。

异常处理是一种特定的程序错误处理机制,是为了让程序员更加关注正常的程序执行序列而设计的。异常处理提供了一种标准的方法以处理错误,发现可预知及不可预知的问题,及允许开发者识别、查出和修改错漏之处。

处理错误的方法有如下几个特点:

(1)不需要打乱程序的结构,如果没有任何错误产生,那么程序的运行不受任何影响。
(2)不依靠方法的返回值来报告错误是否产生。
(3)采用集中的方式处理错误,能够根据错误种类的不同来进行对应的错误处理操作。

下面列出的是Java中几个常见的异常,括号内所注的英文是对应的异常处理类名称:

(1)算术异常(ArithmeticException):当算术运算中出现了除以零这样的运算就会出这样的异常。
(2)空指针异常(NullPointerException):没有给对象开辟内存空间却使用该对象时会出现空指针异常。
(3)文件未找到异常(FileNotFoundException):当程序试图打开一个不存在的文件进行读写时将会引发该异常。经常是由于文件名给错,或者要存储的磁盘、CD-ROM等被移走,没有放入等原因造成。
(4)数组下标越界异常(ArrayIndexOutOfBoundsException):对于一个给定的大小的数组,如果数组的索引超过上限或低于下限都造成越界。
(5)内存不足错误(OutOfMemoryException):当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。

Java的异常处理机制也秉承着面向对象的基本思想。在Java中,所有的异常都是以类的类型存在。 除了内置的异常类之外,Java也可以自定义异常类。此外,Java的异常处理机制也允许自定义抛出异常。

Java通过面向对象的方法来处理异常。在一个方法的运行过程中,如果发生了异常,则这个方法生成代表该异常的一个对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理这一异常。我们把生成异常对象并把它提交给运行时系统的过程称之为异常的抛出(throw)。运行时系统在方法的调用栈中查找,从生成异常的方法开始进行回溯,直到找到包含相应异常处理的方法为止,这一过程称之为异常的捕获(catch)。

下面,来看一个简单的异常范例:

package com.xy.exception;

public class ArrayIndexException {
	public static void main(String[] args) {
		int[] arr = new int[5];
		arr[10] = 7;
		System.out.println("end of main() method!");
	}
}

【结果】
在这里插入图片描述
异常产生的原因在于,数组的下标值超出了最大允许的范围。Java检测这个异常之后,便由系统抛出“ArrayIndexOutOfBoundsException”,用来表示错误的原因,并停止运行程序。

如果没有编写相应的处理异常的程序代码,Java的默认异常处理机制会先抛出异常,然后停止运行程序。在出现异常之后,异常语句之后的代码将不再执行,而是直接结束了程序的运行,那么就表示此时程序是处于一种“不健康”的状态。

为了保证程序出现异常之后,依然可以“善始善终”地完结,就需要引入异常处理操作。

2、异常的处理

异常的处理
在【范例19-1】的异常发生后,Java便把这个异常抛了出来,可是抛出来之后没有程序代码去捕捉它,所以程序到第6行便结束,因此根本不会执行到第7行。如果加上捕捉异常的程序代码,则可针对不同的异常做妥善的处理,这种处理的方式称为异常处理

异常处理是由try、catch与finally等3个关键字所组成的程序块,其语法如下所示(方括号内的部分是可选部分):

try {
    // 要检查的程序语句;
}
catch(异常类 对象名称) {
    // 异常发生时的处理语句;
}
[
catch(异常类 对象名称) {
    // 异常发生时的处理语句;
}
catch(异常类 对象名称) {
    // 异常发生时的处理语句;
}
......
]
[finally {
    // 一定会运行到的程序代码;
    }]

Java提供了try(尝试)、catch(捕捉)及finally(最终)这3个关键词来处理异常。这3个动作描述了异常处理的3个流程。

(1)首先,我们把所有可能发生异常的语句都放到一个try之后由{ }所形成的区块称为“try区块(”try block)。程序通过try{}区块准备捕捉异常。try程序块若有异常发生,程序的运行便中断,并抛出“异常类所产生的对象”。

(2)抛出的对象如果属于catch()括号内欲捕获的异常类,catch则会捕捉此异常,然后进入catch的块里继续运行。

(3)无论try程序块是否捕捉到异常,或者捕捉到的异常是否与catch()括号里的异常相同,最终一定会运行finally块里的程序代码。

(4)finally的程序代码块运行结束后,程序再回到try-catch-finally块之后继续执行。

(5)在异常捕捉的过程中至少做了两个判断:第1个是try程序块是否有异常产生,第2个是产生的异常是否和catch()括号内欲捕捉的异常相同。

(6)finally块是可以省略的。如果省略了finally块,那么在catch()块运行结束后,程序将跳到try-catch块之后继续执行。

根据这些基本概念与运行的步骤,可绘制出下图所示的流程:

在这里插入图片描述

异常处理格式之中可以分为三类:
(1)try{}…catch{}
(2)try{}…catch{}…finally{}
(3)try{}…finally{}

在处理各种异常,需要用到对应的“异常类”,“异常类”指的是由程序抛出的对象所属的类。

例如,“ArrayIndexOutOfBoundsException”就是众多异常类的一种。

package com.xy.exception;

public class DealException {
	public static void main(String[] args) {
		try {	// 检查这个程序的代码
			int[] arr = new int[5];
			arr[10] = 7;	// 这里会出现异常
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("数组超出绑定范围!");
		}
		finally {	// 这个程序的代码一定会执行
			System.out.println("end of main() method!");
		}
	}
}

【结果】
在这里插入图片描述

3、异常类的使用

package com.xy.exception;

public class ExceptionObject {
	public static void main(String[] args) {
		try {	// 检查这个程序的代码
			int[] arr = new int[5];
			arr[10] = 7;	// 这里会出现异常
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("数组超出绑定范围!");
			System.out.println("异常:" + e);
		}
		finally {	// 这个程序的代码一定会执行
			System.out.println("end of main() method!");
		}
	}
}

【结果】
在这里插入图片描述
在第9行中,可以把catch()视为一个专门捕获异常的方法,而括号内的内容可视为方法的参数,而e就是ArrayIndexOutOfBoundsException类所实例化的对象。对象ex接收到由异常类所产生的对象之后,就进到第11行,输出“数组超出绑定范围!”这一字符串,然后在第12行输出异常所属的种类—java.lang.ArrayIndexOutOfBoundsException,其中java.lang是ArrayIndexOutOfBoundsException类所属的包。

值得注意的是,如果想得到详细的异常信息,则需要使用异常对象的printStackTrace()方法。

ex.printStackTrace();

public class ExceptionObject {
	public static void main(String[] args) {
		try {	// 检查这个程序的代码
			int[] arr = new int[5];
			arr[10] = 7;	// 这里会出现异常
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("数组超出绑定范围!");
			System.out.println("异常:" + e);
			e.printStackTrace();
		}
		finally {	// 这个程序的代码一定会执行
			System.out.println("end of main() method!");
		}
	}
}

【结果】
在这里插入图片描述

由运行结果可以看出,printStackTrace()方法给出了更为详细的异常信息,不仅包括异常的类型,还包括异常发生在哪个所属包、哪个所属类、哪个所属方法以及发生异常的行号。

下面来看下try后跟多个catch的情况:

package com.xy.exception;

public class ParseIntException {
	public static void main(String[] args) {
		System.out.println("-----A 计算开始之前-----");
		try {
			int[] arr = new int[5];
			arr[0] = 3;	
			arr[1] = 0;	// 除数为0,有异常
//			arr[10] = 7;	// 数组下标越界,有异常
			int result = arr[0]/arr[1];
			System.out.println("-----B 除法计算结果-----");
			System.out.println("result:" + result);
		}
		catch(ArithmeticException e) {
			e.printStackTrace();
		}
		catch(ArrayIndexOutOfBoundsException e) {
			e.printStackTrace();
		}
		finally {
			System.out.println("-----C 此处不管是否出错都会执行-----");
		}
		System.out.println("-----D 计算结束-----");
	}
}

【结果】
在这里插入图片描述

如果我们取消“ arr[10] = 7; ”前面的注释,结果就会如下:
在这里插入图片描述

4、异常处理机制小结

当异常发生时,通常可用两种方法来处理,一种是交由Java默认的异常处理机制做处理。但这种处理方式,Java通常只能输出异常信息,接着便终止程序的运行,接着结束TestException的运行。

另一种处理方式是用自行编写的try-catch-finally块来捕捉异常*。自行编写程序代码来捕捉异常最大的好处是,可以灵活操控程序的流程,且可做出最适当的处理。

下图绘出了异常处理机制的选择流程。

在这里插入图片描述

5、异常类的处理流程

异常可分为两大类:java.lang.Exception类java.lang.Error类这两个类均继承自java.lang. Throwable类

下图为Throwable类的继承关系图:

在这里插入图片描述

习惯上将Error类与Exception类统称为异常类,但两者本质上还是不同的。Error类通常指的是Java虚拟机(JVM)出错,用户无法在程序里处理这种错误。

不同于Error类,Exception类包含了一般性的异常,这些异常通常在捕捉到之后便可做妥善的处理,以确保程序继续运行。“ArrayIndexOutOfBoundsException”就是属于这种异常。

为了更好地说明Java之中异常处理的操作特点,下面给出异常处理的流程:

(1)如果程序之中产生了异常,那么会自动地由JVM根据异常的类型,实例化一个指定异常类的对象;如果这个时候程序之中没有任何的异常处理操作,则这个异常类的实例化对象将交给JVM进行处理,而JVM的默认处理方式就是进行异常信息的输出,而后中断程序执行。

(2)如果程序之中存在了异常处理,则会由try语句捕获产生的异常类对象;然后将该对象与try之后的catch进行匹配,如果匹配成功,则使用指定的catch进行处理,如果没有匹配成功,则向后面的catch继续匹配,如果没有任何的catch匹配成功,则这个时候将交给JVM执行默认处理。

(3)不管是否有异常都会执行finally程序,如果此时没有异常,执行完finally,则会继续执行程序之中的其他代码,如果此时有异常没有能够处理(没有一个catch可以满足),那么也会执行finally,但是执行完finally之后,将默认交给JVM进行异常的信息输出,并且程序中断。

6、异常处理的标准格式

点击 throw、throws
下面来看下throws和throw配合使用的例子:

package com.xy.exception;

public class ThrowsAndThrow {
	public static void main(String[] args) {
		int[] arr = new int[5];
		try {
			setZero(arr, 10);
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("数组超出绑定范围!");
			System.out.println("异常:" + e);
		}
		System.out.println("end of main() method!");
	}
	
	private static void setZero(int[] arr, int index) throws ArrayIndexOutOfBoundsException {
		System.out.println("方法setMethod()开始!");
		try {
			arr[index] = 0;
		}
		catch(ArrayIndexOutOfBoundsException e) {
			throw e;
		}
		finally {
			System.out.println("方法setMethod()结束!");
		}
	}
}

【结果】
在这里插入图片描述

使用throw抛出异常。throw总是出现在方法体中,一旦它抛出一个异常,程序会在throw语句后立即终止,后面的语句就没有机会执行,然后在包含它的所有try块中(可能在上层调用方法中)从里向外寻找含有与其异常类型匹配的catch块,然后加以处理。

7、异常类型的继承关系

异常类型的最大父类是Throwable类,其分为两个子类,分别为Exception、Error。Exception表示程序可处理的异常,而Error表示JVM错误,一般无需程序开发人员自己处理。

8、RuntimeException和Exception的区别

请解释Exception和RuntimeException的区别:
· Exception:强制性要求用户必须处理;
· RuntimeException:是Exception的子类,由用户选择是否进行处理。

猜你喜欢

转载自blog.csdn.net/qq_43555323/article/details/84938964