异常指不期而至的各种状况,如:文件找不到、网络连接失败、除0操作、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。
概念
Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。
在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。

从图中可以看出 : 所有异常类型都是Throwable的子类,因而Throwable在异常类的层次结构的顶层。
Throwable分成了两个不同的分支,一个分支是Error,它表示不希望被程序捕获或者是程序无法处理的错误。 另一个分支是Exception,它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。
其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常(其他类,包括自定义异常类)。下面将详细讲述这些异常之间的区别与联系:
Error
Error:Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关,如系统崩溃、虚拟机错误、动态链接失败等。
例如,Java虚拟机运行错误(VirtualMachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通常是使用Error的子类描述。
- 在应用程序的控制和处理能力之外, Java 虚拟机生成并抛出,
Exception
Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而RuntimeException之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。。
注意:
Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。RuntimeException运行时异常编译器无法检查报错,非运行时异常编译器无法通过。
checkedException
检查异常: 在正确的程序运行过程中,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理。
除了
RuntimeException及其子类以外,其他的Exception类及其子类都属于这种异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。
unCheckedException
不受检查异常:包括RuntimeException及其子类和Error。
不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常。
处理机制
Java的异常处理本质上 是抛出异常和捕获异常。
涉及到五个关键字,分别是:try、catch、finally、throw、throws。
关键词try后的一对大括号将一块可能发生异常的代码包起来,为监控区域。
Java方法在运行过程中发生了异常,则创建异常对象。将异常抛出监控区域之外,由Java运行时系统负责寻找匹配的catch子句来捕获异常。
若有一个catch语句匹配到了,则执行该catch块中的异常处理代码,就不再尝试匹配别的catch块了。故在编写异常抛出时,将RuntimeException等相对高层的异常类放在最后面,优先捕获底层异常类。
举个算数异常的例子(除数为零):
@Test
public void test01() {
int a = 1;
int b = 0;
try {
if (b == 0) {
throw new ArithmeticException();
}
System.out.println("a/b的值是:" + a / b);
System.out.println("this will not be printed!");
}
catch (Exception e) { // catch捕捉异常
System.out.println("程序出现异常,变量b不能为0!");
System.out.println(e);
}
System.out.println("程序正常结束。");
}
/*
输出结果:
程序出现异常,变量b不能为0!
java.lang.ArithmeticException
程序正常结束。
*/
根据前面讲述的,算术异常属于运行时异常,因而实际上该异常不需要程序抛出,运行时系统自动抛出,将例子改为如下:
@Test
public void test02() {
int a = 1;
int b = 0;
System.out.println("a/b的值是:" + a / b);
System.out.println("this will not be printed!");
}
/*
ArithmeticException是运行时异常,因而实际上该异常不需要程序抛出,运行时系统自动抛出
输出结果如下:
java.lang.ArithmeticException: / by zero
at TestException.test02(TestException.java:36)
*/
throw
前面举例,我们只是获取了被Java运行时系统引发的异常。然而,我们还可以用throw语句抛出明确的异常。Throw的语法形式如下: throw ThrowableInstance;
这里的ThrowableInstance一定是Throwable类类型或者Throwable子类类型的一个对象。简单的数据类型,例如int,char,以及非Throwable类,例如String或Object,不能用作异常。
有两种方法可以获取Throwable对象:在catch子句中使用参数或者使用new操作符创建。
程序执行完throw语句之后立即停止;throw后面的任何语句不被执行,最邻近的try块用来检查它是否含有一个与异常类型匹配的catch语句。如果发现了匹配的块,控制转向该语句;如果没有发现,次包围的try块来检查,以此类推。如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。
创建并抛出异常:
@Test
public void test05() {
try{
proc();
}catch(NullPointerException e){
System.out.println("Recaught: "+e);
}
}
private void proc(){
try{
throw new NullPointerException("demo");
}catch(NullPointerException e){
System.out.println("Caught inside proc");
throw e;
}
}
/*
Caught inside proc
Recaught: java.lang.NullPointerException: demo
*/
throw new NullPointerException("demo");提示
此处
new用来构造一个NullPointerException实例,所有的Java内置的运行时异常有两个构造方法:一个没有参数,一个带有一个字符串参数。当用第二种形式时,参数指定描述异常的字符串。如果对象用作print()或者println()的参数时,该字符串被显示。这同样可以通过调用getMessage()来实现,getMessage()是由Throwable定义的。
throws
如果一个方法导致异常但不处理它,必须throws声明可能产生的异常,抛出给调用者。
Throws抛出异常的规则:
如果是不受检查异常(
unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。必须声明方法可抛出的任何检查异常(
checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
finally
finally块无论有没有异常抛出都会执行。如果抛出异常,即使没有catch子句匹配,finally也会执行。
finally子句在方法返回之前执行。这在关闭文件句柄和释放任何在方法开始时被分配的其他资源是很有用。
/**
* @author yt
* @version 1.0
* @date 2019/8/17/017 7:07
* @Description TODO
*/
public class TestFinally {
static void proc1(){
try{
System.out.println("inside proc1");
throw new RuntimeException("demo");
}finally{
System.out.println("proc1's finally");
}
}
static void proc2(){
try{
System.out.println("inside proc2");
return ;
} finally{
System.out.println("proc2's finally");
}
}
static void proc3(){
try{
System.out.println("inside proc3");
}finally{
System.out.println("proc3's finally");
}
}
public static void main(String [] args){
try{
proc1();
}catch(Exception e){
System.out.println("Exception caught: " + e);
}
proc2();
proc3();
}
}
/*
inside proc1
proc1's finally
Exception caught: java.lang.RuntimeException: demo
inside proc2
proc2's finally
inside proc3
proc3's finally
*/
异常链
异常链顾名思义就是将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。 Java API文档中给出了一个简单的模型:
try {
lowLevelOp();
} catch (LowLevelException le) {
throw (HighLevelException) new HighLevelException().initCause(le);
}
当程序捕获到了一个底层异常,在处理部分选择了继续抛出一个更高级别的新异常给此方法的调用者。 这样异常的原因就会逐层传递。这样,位于高层的异常递归调用getCause()方法,就可以遍历各层的异常原因。 这就是Java异常链的原理。异常链的实际应用很少,发生异常时候逐层上抛不是个好注意, 上层拿到这些异常又能奈之何?而且异常逐层上抛会消耗大量资源, 因为要保存一个完整的异常链信息。
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
在程序中使用自定义异常类,大体可分为以下几个步骤:
- 创建自定义异常类。
- 在方法中通过
throw关键字抛出异常对象。 - 如果在当前抛出异常的方法中处理异常,可以使用
try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。 - 在出现异常方法的调用者中捕获并处理异常。
举例自定义异常:
class MyException extends Exception {
private int detail;
MyException(int a){
detail = a;
}
public String toString(){
return "MyException ["+ detail + "]";
}
}
public class TestMyException{
static void compute(int a) throws MyException{
System.out.println("Called compute(" + a + ")");
if(a > 10){
throw new MyException(a);
}
System.out.println("Normal exit!");
}
public static void main(String [] args){
try{
compute(1);
compute(20);
}catch(MyException me){
System.out.println("Caught " + me);
}
}
}
/*
Called compute(1)
Normal exit!
Called compute(20)
Caught MyException [20]
*/