JAVA 异常详解

1.引言

在java中,异常会导致运行时错误。异常就是一个表示阻止程序正常执行的错误或情况。如果异常没有被处理,那么程序将会非正常终止。一个简单的例子是“除法”。如可能被零除,就有必要进行检查,确保程序不会冒进,并在那种情况下执行除法。但具体通过什么知道分母是零呢?在那个特定的方法里,在我们试图解决的那个问题的环境中,我们或许知道该如何对待一个零分母。但假如它是一个没有预料到的值,就不能对其进行处理,所以必须产生一 个异常,而非不顾一切地继续执行下去。

异常控制形式:

try {
    // 要保卫的区域:
    // 可能“掷”出 A,B,或C 的危险情况
} catch (A a1) {
    // 控制器 A
} catch (B b1) {
    // 控制器 B
} catch (C c1) {
    // 控制器 C
} 

try块包含正常情况下执行的代码,catch块包含异常情况下执行的代码。异常处理将错误处理代码从正常的程序设计任务中分离出来,这样,可以使程序更易读和更易修改。

生成并抛出一个异常:

throw new Exception();

产生一个异常时,会发生几件事情。首先,按照与创建Java 对象一样的方法创建异常对象:在内存“堆” 里,使用new 来创建。随后,停止当前执行路径(记住不可沿这条路径继续下去),然后从当前的环境中释放出异常对象的句柄。此时,异常控制机制会接管一切,并开始查找一个恰当的地方,用于继续程序的执行。这个恰当的地方便是“异常控制器”,它的职责是从问题中恢复,使程序要么尝试另一条执行路径,要 么简单地继续。

当一条语句会抛出异常时,要么在它所在的函数里用try-catch处理,要么让它所在的函数重新抛出这个异常让这个函数的调用者捕获处理它。

public class ThrowException {
	public static void main(String[] args) {
		try {
			m1();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void m1() throws Exception {
		throw new Exception();
	}
	
	public static void m2()  {
		try {
			throw new Exception();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

2.异常类型

下面是java异常类型的类图

Throwable是所有异常类的根。所有java异常类都直接或间接继承自Throwable。可以通过扩展Exception或它的子类来创建自己的异常类。对于程序员常用的所有异常类来说,由于 Exception 类是它们的基础,所以我们不会获得关于异常太多的信 息,但可调用来自它的基础类 Throwable 的方法: String getMessage() 获得详细的消息。 String toString() 返回对 Throwable 的一段简要说明,其中包括详细的消息(如果有的话)。 void printStackTrace() void printStackTrace(PrintStream) 打印出 Throwable 和 Throwable 的调用堆栈路径。调用堆栈显示出将我们带到异常发生地点的方法调用的顺 序。

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

    1)系统错误是有java虚拟机抛出的,用Error表示。Error类描述的是内部系统错误。这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。

    2)异常是用Exception类表示的。它描述的是由程序的外部环境引起的错误,这些错误可以被程序捕获和处理。

    3)运行时异常是用RuntimeException类表示的,他描述的是程序设计错误。如错误的类型转换、越界访问数组。是由java虚拟机抛出的。

RuntimeException和Error以及它们的子类都称为免检异常。所有其他异常都称为必检异常,意思是编译器会强制程序员检查并处理他们。

在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。如,如果通过一个引用变量访问一个对象之前并未将一个对象赋值给它,就会抛出NullPointerException异常。这些都是程序必须纠正的逻辑错误。免检异常可能在程序的任何一个地方出现。为避免过多的使用try-catch块,java允许不编写代码去捕获或声明免检异常。如果不捕获这些异常,又会出现什么情况呢?由于编译器并不强制捕获它们,假如不捕获的话,一个RuntimeException 可能过滤掉我们到达 main()方法的所有途径,即当发生RuntimeException异常时如果不捕获,就会终止程序,并打印出错信息。

3.创建自己的异常

经常都需要创建自己的异常,以便指出自己的库可能生成的一个特殊错误。 为创建自己的异常类,必须从一个现有的异常类型继承——最好在含义上与新异常近似。

class MyException extends Exception {
 public MyException() {}
 public MyException(String msg) {
 super(msg);
 }
}

这里的关键是“extends Exception”,它的意思是:除包括一个 Exception 的全部含义以外,还有更多的含 义。增加的代码数量非常少——实际只添加了两个构建器,对 MyException 的创建方式进行了定义。请记 住,假如我们不明确调用一个基础类构建器,编译器会自动调用基础类默认构建器。在第二个构建器中,通过使用 super 关键字,明确调用了带有一个 String 参数的基础类构建器。 

由于异常不过是另一种形式的对象,所以可以继续这个进程,进一步增强异常类的能力。但要注意,对使用自己这个包的客户程序员来说,他们可能错过所有这些增强。因为他们可能只是简单地寻找准备生成的异常,除此以外不做任何事情——这是大多数 Java 库异常的标准用法。若出现这种情况,有可能创建一个新异常类型,其中几乎不包含任何代码:

class SimpleException extends Exception {
}

它要依赖编译器来创建默认构建器(会自动调用基础类的默认构建器)。当然,在这种情况下,我们不会得到一个 SimpleException(String)构建器,但它实际上也不会经常用到。

4.异常的限制

 重写一个方法时,只能产生已在方法的基础类版本中定义的异常。这是一个重要的限制,因为它意味着与基础类协同工作的代码也会自动应用于从基础类衍生的任何对象(当然,这属于基本的 OOP 概念),其中包括异常。即用于一个特定方法的“异常接口”可能在继承和覆盖时变得更“窄”,但它不会变得更“宽”

  • 重写的方法可以抛出父类方法所抛出的异常或它的子类型
  • 重写的方法可以不用抛出父类方法所抛出的异常
  • 重写的方法不可以抛出异常如果父类方法没有抛出异常

对异常的限制并不适用于构建器。一个构建器能够“掷”出它希望的任何东 西,无论基础类构建器“掷”出什么。然而,由于必须坚持按某种方式调用基础类构建器,所以衍生类构建器必须在自己的异常规范中声明所有基础类构建器异常。

5.finally子句

有时候,不论异常是否出现或者是否被捕获, 都希望执行某些代码。java的finally子句可以用来达到者目的,如为确保一个文件在所有情况下均已关闭,可能要在finally块中放置一条关闭语句。完整的异常控制是下面这个样子:

try {
    // 要保卫的区域:
    // 可能“掷”出 A,B,或C 的危险情况
} catch (A a1) {
    // 控制器 A
} catch (B b1) {
    // 控制器 B
} catch (C c1) {
    // 控制器 C
} finally {
    // 每次都会发生的情况
}

任何情况下,finally块中的代码都会执行,即使到达finally块前有return语句。如下代码:

public class FinallyWork {
	static int count = 0;

	public static void main(String[] args) {

		while (true) {
			try {
				// post-increment is zero first time:
				if (count++ == 0)
					throw new Exception();
				System.out.println("No exception");
			} catch (Exception e) {
				System.out.println("Exception thrown");
				return;
			} finally {
				System.out.println("in finally clause");
				if (count == 2)
					break; // out of "while"
			}
		}
	}

输出:

Exception thrown
in finally clause 

一般情况下,Java 的异常实施方案都显得十分出色。不幸的是,它依然存在一个缺点。尽管异常指出程序里 存在一个危机,而且绝不应忽略,但一个异常仍有可能简单地“丢失”。在采用 finally 从句的一种特殊配 置下,便有可能发生这种情况:

class VeryImportantException extends Exception {
	public String toString() {
		return "A very important exception!";
	}

}

class HoHumException extends Exception {
	public String toString() {
		return "A trivial exception";
	}
}

public class LostMessage {
	void f() throws VeryImportantException {
		throw new VeryImportantException();
	}

	void dispose() throws HoHumException {
		throw new HoHumException();
	}

	public static void main(String[] args) throws Exception {
		LostMessage lm = new LostMessage();
		try {
			lm.f();
		} finally {
			lm.dispose();
		}
	}
} 

输出:

Exception in thread "main" A trivial exception
    at com.me.exception.LostMessage.dispose(LostMessage.java:31)
    at com.me.exception.LostMessage.main(LostMessage.java:39)

可以看到丢失了 VeryImportantException异常

6.异常匹配

“掷”出一个异常后,异常控制系统会按当初编写的顺序搜索“最接近”的控制器。一旦找到相符的控制器,就认为异常已得到控制,不再进行更多的搜索工作。在异常和它的控制器之间,并不需要非常精确的匹配。一个衍生类对象可与基础类的一个异常控制器相配,即我们在写代码时,将子类写在前面。

7.异常使用准则

(1) 解决问题并再次调用造成异常的方法。
(2) 平息事态的发展,并在不重新尝试方法的前提下继续。
(3) 计算另一些结果,而不是希望方法产生的结果。
(4) 在当前环境中尽可能解决问题,以及将相同的异常重新“掷”出一个更高级的环境。
(5) 在当前环境中尽可能解决问题,以及将不同的异常重新“掷”出一个更高级的环境。
(6) 中止程序执行。
(7) 简化编码。若异常方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。
(8) 使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改善应用程序的健壮性)

8.总结

通过先进的错误纠正与恢复机制,我们可以有效地增强代码的健壮程度。对我们编写的每个程序来说,错误恢复都属于一个基本的考虑目标。它在Java 中显得尤为重要,因为该语言的一个目标就是创建不同的程序组件,以便其他用户(客户程序员)使用。为构建一套健壮的系统,每个组件都必须非常健壮。在Java 里,异常控制的目的是使用尽可能精简的代码,创建大型、可靠的应用程序,同时排除程序里那些不能控制的错误。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值