6.2错误和异常处理

Error 和 Exception

在这里插入图片描述
所有异常对象的基类是java.lang.Throwable的,以及它的两个子类java.lang.Exception和java.lang.Error。

Error和Exception其实都是错误,只不过error是内部错误:程序员通常无能为力,一旦发生,就要想办法让程序优雅的结束,而异常是自己程序导致的问题,可以捕获并且处理。

错误的种类

  1. 用户输入错误: 除了不可避免的打字错误外,一些用户喜欢开拓自己的道路,而不是跟随指导输入,例如:用户要求连接到语法错误的URL,网络层会抱怨。
  2. 设备错误:设备错误可分为许多种,以下四种比较有代表性: 硬件并不总是做你想做的事, 打印机可能会已经被关闭了导致你无法使用, 网页可能暂时不可用, 在任务的中间,设备往往会失效。
  3. 物理限制:我们使用的电脑内存,电脑磁盘等空间都是有限的,所以磁盘可能被装满,我们也可能耗尽内存。

在这里插入图片描述
上图是Java的错误类
其中较为典型的有

  1. VirtualMachineError:** 抛出以指示Java虚拟机已损坏或已耗尽继续运行所需的资源**,又可以细分为:
    OutOfMemoryError:当Java虚拟机无法分配对象时抛出,因为它内存不足,并且垃圾收集器无法提供更多内存
    StackOverflowError: 当堆栈溢出发生时抛出,因为应用程序重复得太深.
  2. LinkageError: 一个类在某种程度上依赖于另一个类;但是,在编译前一个类之后,后一个类发生了不兼容的变化,如NoClassDefFoundError: 如果JVM或Class Loader实例试图在类的定义中加载,但无法找到定义,则抛出这个错误。
    无论是哪种错误,我们都无法处理,因此我们需要专注于我们能处理的部分:Exception

如何处理Exception

首先看一个例子
在这里插入图片描述
异常指的是程序执行过程中的非正常事件,会导致程序无法在按预想的流程执行,而Exception将错误信
息传递给上层调用者,并报告“案发现场”的信息,同时退出程序,这是return之外的第二种退出途径。如果没有异常查找,遇到异常时程序的退出会按照以下步骤:

  1. 方法抛出一个对象,该对象封装错误信息。
  2. 方法立即退出,不返回任何值。
  3. 此外,调用抛出错误的方法的代码不会恢复执行。
    有异常查找之后:
    遇到异常时,异常处理机制会开始搜索能够处理此特定错误条件的异常处理程序,若找不到异常处理程序,整个系统完全退出

异常处理机制的益处

  1. 使你不会忘记处理常见的错误,我们在代码中设置的flag,或者特殊的返回值就是为了提醒我们处理这样的可能发生的常见错误。
  2. 提供错误的高级摘要和堆栈跟踪,比如C语言中的核心转储就有这种功能。
  3. 优化代码结构,把处理异常的代码和一般的代码路径分离的同时,还可以减轻从失效中恢复的负担。
  4. 轻松编写具有健壮性、可维护性的代码
/**
*假如不用异常处理机制,那么正常的代码就会和错误处理代*码交织在一起,使得结构十分臃肿不清晰
/
FileInputStream fIn = new FileInputStream(fileName);
if (fIn == null) {
	switch (errno) {
		case _ENOFILE:
		System.err.println(“File not found: “ + …);
		return -1;
		default:
		System.err.println(“Something else bad happened: “ + …);
		return -1;
	} 
}
DataInput dataInput = new DataInputStream(fIn);
if (dataInput == null) {
	System.err.println(“Unknown internal error.”);
	return -1; // errno > 0 set by new DataInputStream
}
int i = dataInput.readInt();
if (errno > 0) {
	System.err.println(“Error reading binary data from file”);
	return -1;
}
return i;




/**
*使用异常处理机制的益处:明显的优化了结构,**把正常的逻辑代码和错误处理代码分离**。
/
FileInputStream fileInput = null;
try {
	fileInput = new FileInputStream(fileName);
	DataInput dataInput = new DataInputStream(fileInput);
	return dataInput.readInt();
} catch (FileNotFoundException e) {
	System.out.println("Could not open file " + fileName);
} catch (IOException e) {
	System.out.println("Couldn’t read file: " + e);
} finally {
	if (fileInput != null) 
		fileInput.close();
}

异常的分类

从java类库角度分类

在这里插入图片描述
异常层次可以分成两个分支:
运行时异常和其他异常,一般来说,运行时异常是由程序员在代码里处理不当造成的,而其他异常是由于外部原因造成的,发生其他异常时,你的程序实际上是很好的,但是就是因为外部因素的作用,例如I/O(输入/输出)错误,使得程序出现异常

从RuntimeException(运行时异常)继承的异常包含的问题有:错误的类型转换,数组越界访问,空指针等等。
而其他异常则由这些问题引出: 尝试读取文件的末尾, 尝试打开一个不存在的文件, 试图为不表示现有类的字符串找到Class对象,例如Class cl = Class.forName(“java.util.fooxxx”)

运行时异常的避免

首先,运行时异常是程序源代码中引入的故障造成的,因此如何避免源代码的故障就是避免运行时异常的关键,例如: 通过测试数组索引与数组边界之间的关系,可以避免ArrayIndexofBoundsException; 如果在使用该变量之前检查该变量是否为空,则不会发生NullPointerException(我们常说的空指针异常)。如果在代码中提前进行验证,这些故障就可以避免
不过,非运行时异常,是程序员无法完全控制的外在问题所导致的,即使在代码中提前加以验证(文件是否存在),也无法完全避免失效发生。

从异常被谁处理的角度分类

在这里插入图片描述
当异常发生时,我们要么处理(使用try-catch-finally),要么声明我们无法处理这种类型的异常(使用throws语句),并且把异常的处理交给编译器,实际上,编译器可帮助检查你的程序是否已抛出或处理了可能的异常,当编译器也无法处理异常的时候,编译器也会声明异常无法处理并且使用throws语句抛出。也就是说编译器会处理上图中蓝色部分的异常,而不会处理上图中红色部分的异常。
这样,我们可以把异常分为可被编译器处理和不可被编译器处理两大类,IOExceptuon可被编译器处理,而Errors和RuntimeExceptions不可以被编译器处理。 Errors表示应用程序之外发生的情况,例如系统崩溃。 运行时异常通常是由应用程序逻辑中的故障发生的。 在这种情况下,你不能做任何事情,但必须重写你的程序代码。 所以编译器不会检查这些。 运行时异常将在开发和测试期间发现。 然后我们必须重构代码以消除这些错误。

Unchecked exceptions

在这里插入图片描述
可以不处理,编译没问题,但执行时出现就会导致程序失败,代表程序中的潜在bug类似于编程语言中的
dynamic type checking
比较富有代表性的unchecked exception有ArrayIndexOutOfBoundsException,NullPointerException,NumberFormatException,
ClassCastException,IllegalArgumentException,NoClassDefFoundError

public class NullPointerExceptionExample {
	public static void main(String args[]){
		String str=null;
		System.out.println(str.trim()); } }
Exception in thread"main"java.lang.NullPointerException
public class ArrayIndexOutOfBoundExceptionExample {
	public static void main(String args[]){
		String strArray[]={"Arpit","John","Martin"};
		System.out.println(strArray[4]); 
	} 
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4

checked exceptions

在这里插入图片描述
必须捕获并指定错误处理器handler,否则
编译无法通过,类似于编程语言中的static type checking
下面是checked exceptions的一些实例

public static void main(String args[]) {
	FileInputStream fis = null;
	try {
	fis = new FileInputStream("sample.txt");
	int c;
	while ((c = fis.read()) != -1)
		System.out.print((char) c);
	fis.close(); 
	} catch (FileNotFoundException e) { 
		e.printStackTrace(); 
	} catch (IOException e) { 			
		e.printStackTrace(); 
	} catch (Exception e) { 
		e.printStackTrace(); 
	} 
}
public static void main(String args[]) throws IOException {
	FileInputStream fis = null;
	fis = new FileInputStream("sample.txt");
	int k;
	while ((k = fis.read()) != -1)
		System.out.print((char) k);
	fis.close(); 
}

到底使用checked exception还是unchecked exception

Unchecked异常也可以使用throws声明或try/catch进行捕获,但大多数时候是不需要的,也不应该这么做——有的时候上策是掩耳盗铃,对发现的编程错误充耳不闻。而到底使用unchecked exception还是checked exception,取决于客户端,如果客户端可以通过其他的方法恢复异常,那么采用checked exception;如果客户端对出现的这种异常无能为力,那么采用unchecked exception;异常出现的时候,要做一些试图恢复它的动作而不要仅仅的打印它的信息。
原则上,尽量使用unchecked exception来处理编程错误:因为unchecked exception不用使客户端代码显式的处理它们,它们自己会在出现的地
方挂起程序并打印出异常信息。这样就可以充分利用Java API中提供的丰富unchecked exception,如
NullPointerException , IllegalArgumentException和
IllegalStateException等,使用这些标准的异常类而不需亲自创建新的异常类,使代码易于理解并避免过多消耗内存。如果client端对某种异常无能为力,可以把它转变为一个unchecked exception,程序被挂起并返回客户端异常信息。我们的原则是不要创建没有意义的异常,client应该从checked exception中获取更有价值的信息(案发现场具体是什么样子),利用异常返回的信息来明确操作失败的原因。而如果client仅仅想看到异常信息,可以简单抛出一个unchecked exception。
但是也有必须使用checked exception的时候: 错误可预料,但无法预防,但可以有手段从中恢复,此时使用checked exception。而如果做不到这一点,则使用unchecked exception。这里解释一下不可预防:不可预防指脱离了程序的控制范围。
如果读文件的时候发现文件不存在了,可以让用户选择其他文件;但是如果调用某方法时传入了错误的参数,则无论如何都无法在不中止执行的前提下进行恢复。
总之,使用checked exception的必要条件如下:

  1. 错误可预料,但无法预防,但可以有手段从中恢复
  2. 可合理的恢复
    使用checked/unchecked异常的原则:
    对于可恢复/期望恢复的情况,抛出checked异常
    对于程序错误、不确定是否可恢复,抛出unchecked 异常
    尽可能在checked异常中提供方法和丰富的信息,以便协助恢复

在这里插入图片描述

通过抛出声明检查异常

“异常”也是方法和client端之间spec的一部分,在post-condition中刻画, 试图从文件中读取的代码知道该文件可能不存在,或者它可能是空的。 因此,试图处理文件中信息的代码将需要通知COM 它可以抛出某种IOException, 声明方法可以抛出异常的地方是方法的上头;该方法的上头更改以反映方法可以抛出的已检查异常。而在spec中声明抛出异常时需要使用@throws。
在这里插入图片描述
程序员必须在方法的spec中明确写清本方法会抛出的所有checked exception, 以便于调用该方法的client加以处理,而unchecked异常不需要写在spec中
在这里插入图片描述
在一个方法抛出多个checked异常时,都需要在方法签名中列出

class MyAnimation {
	. . .
	public Image loadImage(String s) 
	throws FileNotFoundException, EOFException 
	{
		. . . 
	} 
}

你的方法应该throw什么异常

你所调用的其他函数抛出了一个checked exception——从其他函数传来的异常, 当前方法检测到错误并使用throws抛出了一个checked exception——你自己造出的异常,此时需要告知你的client需要
处理这些异常。如果没有handler来处理被抛出的checked exception,程序就终止执行。
而且,不建议声明Error,RuntimeException类型的异常。
总之,方法要在定义和spec中明确声明所抛出的全部checked exception,没有声明所有抛出checked异常,编译会出错, Unchecked异常和Error可以不用处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值