16.java异常处理

39次阅读

共计 4416 个字符,预计需要花费 12 分钟才能阅读完成。

概念
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用 System.out.println(11/0),那么你是因为你用 0 做了除数,会抛出 java.lang.ArithmeticException 的异常。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java 中的异常可以是函数中的语句执行时引发的,也可以是程序员通过 throw 语句手动抛出的,只要在 Java 程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE 就会试图寻找异常处理程序来处理异常。
异常的体系结构
Java 把异常当作对象来处理,并定义一个基类 java.lang.Throwable 作为所有异常的超类。
在 Java API 中已经定义了许多异常类,这些异常类分为两大类,错误 Error 和异常 Exception。
java 异常层次结构图:

总从图中可以看出所有异常类型都是内置类 Throwable 的子类,因而 Throwable 在异常类的层次结构的顶层。
接下来 Throwable 分成了两个不同的分支,一个分支是 Error,它表示不希望被程序捕获或者是程序无法处理的错误。另一个分支是 Exception,它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。其中异常类 Exception 又分为运行时异常 (RuntimeException) 和非运行时异常。
Java 异常又可以分为非检查异常(Unchecked Exception)和检查异常(Checked Exception)。
下面将详细讲述这些异常之间的区别与联系:

Error:Error 类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通常是使用 Error 的子类描述。
Exception:在 Exception 分支中有一个重要的子类 RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义 ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而 RuntimeException 之外的异常我们统称为非运行时异常,类型上属于 Exception 类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、SQLException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。

Error 和 Exception 的区别
Error 通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java 虚拟机(JVM)一般会选择终止线程;Exception 通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
检测异常和不受检查异常
不受检查异常:包括 RuntimeException 及其子类和 Error。
检查异常:在正确的程序运行过程中,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理。除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于检查异常,当程序中可能出现这类异常,要么使用 try-catch 语句进行捕获,要么用 throws 子句抛出,否则编译无法通过。
不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常。
异常的处理
Java 的异常处理本质上是抛出异常和捕获异常

抛出异常:要理解抛出异常,首先要明白什么是异常情形(exception condition),它是指阻止当前方法或作用域继续执行的问题。其次把异常情形和普通问题相区分,普通问题是指在当前环境下能得到足够的信息,总能处理这个错误。对于异常情形,已经无法继续下去了,因为在当前环境下无法获得必要的信息来解决问题,你所能做的就是从当前环境中跳出,并把问题提交给上一级环境,这就是抛出异常时所发生的事情。抛出异常后,会有几件事随之发生。首先,是像创建普通的 java 对象一样将使用 new 在堆上创建一个异常对象;然后,当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序,这个恰当的地方就是异常处理程序或者异常处理器,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。
捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着 Java 程序的终止。

注意: 对于运行时异常、错误和检查异常,Java 技术所要求的异常处理方式有所不同。

对于方法运行中可能出现的 Error,当运行方法不欲捕捉时,Java 允许该方法不做任何抛出声明。因为,大多数 Error 异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。

由于运行时异常及其子类的不可查性,为了更合理、更容易地实现应用程序,Java 规定,运行时异常将由 Java 运行时系统自动抛出,允许应用程序忽略运行时异常。

对于所有的检查异常,Java 规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉检查异常时,它必须声明将抛出异常。
java 异常处理涉及到五个关键字,分别是:try、catch、finally、throw、throws。
try    — 用于监听。将要被监听的代码 (可能抛出异常的代码) 放在 try 语句块之内,当 try 语句块内发生异常时,异常就被抛出。
* catch  — 用于捕获异常。catch 用来捕获 try 语句块中发生的异常。在编写多个 catch 的时候,捕获范围大的异常一定要放在捕获范围小的异常后面。

finally — finally 语句块总是会被执行。它主要用于回收在 try 块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有 finally 块执行完成之后,才会回来执行 try 或者 catch 块中的 return 或者 throw 语句,如果 finally 中使用了 return 或者 throw 等终止方法的语句,则就不会跳回执行,直接停止。
throw  — 用于抛出一个实例化的异常对象。
throws — 用在方法签名中,用于声明该方法可能抛出的异常,如果方法真的出现异样,将交由调用者来处理。调用了具有 throws 申明的方法之后,不管异常是否出现,调用者必须使用 try…catch 处理。调用声明了运行时异常的函数,可以不强制处理,将交给 jvm 默认处理。

throws 语法如下:
public void info() throws Exception
{
//body of method
}
Exception 是该方法可能引发的所有的异常, 也可以是异常列表,中间以逗号隔开。
Throws 抛出异常的规则:
* 如果是不受检查异常(unchecked exception),即 Error、RuntimeException 或它们的子类,那么可以不使用 throws 关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
* 必须声明可抛出的任何检查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用 try-catch 语句捕获,要么用 throws 子句声明将它抛出,否则会导致编译错误。
* 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
* 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
断言 assert
什么是断言?就是肯定某一个操作的返回结果是正确的,如果程序执行到断言语句的时候,发现断言不正确了,返回结果是错误的了,则通过断言检查肯定,会为用户提示错误的信息。在 jdk1.4 引入了 assert 关键字。断言的使用格式:
assert boolean 表达式;
assert boolean 表达式:详细信息;
默认断言是不会影响程序正常运行的,在断言不成立时,也只会将信息显示出来。除非使用 java -ea 程序 手工启动断言。
自定义异常
虽然 java 已经提供了很多系统性异常,但在开发中还是无法满足实际的需要。所以 java 允许用户自定义异常。语法如下:
class 自定义异常类 extends 系统提供的异常类{
public 构造函数(String msg){
super(msg);
//…
}
//…
}
总结

处理运行时异常时,采用逻辑去合理规避,同时辅助使用 try-catch 处理。
在多重 catch 块后面,加一个 catch(Exception)来处理可能遗漏的异常。
对于不确定的代码,也可以加上 try-catch,处理潜在的异常。
尽量去处理异常,切记只是简单的调用 printStackTrace()去打印输出。
尽量添加 finally 块去释放所占用的资源。

6. 具体如果处理异常,要根据不同的业务需求和异常类型去决定。

正文完
 0