Java基础--异常处理机制

什么是异常

在平时的Java开发中,异常是我们不可避免的。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。因此,Java用它的异常处理机制来解决平时我们在开发过程中所遇到的异常问题。

首先我们通过一张图来了解Java的异常类:

我们可以很容易看出,所有的异常类都有一个共同的父类Throwable。Throwable有两个重要的子类:Exception(异常)和Error(错误),这两者包含了所有我们开发过程中会出现的错误情况。那么问题来了,异常和错误的区别在哪儿。

Exception(异常)

Exception指的是程序本身可以处理的异常。

Exception类有一个重要的子类RuntimeException。RuntimeException类及其子类表示“JVM常用操作”引发的错误,由类名我们也可以知道RuntimeException就是运行时异常,简单来说就是在程序运行过程中所出现的异常,例如:空指针引用、除数为零、数组越界等。这种异常一般是由程序逻辑错误引起的,程序应该从逻辑角度上尽可能避免这类异常发生。它还有一个特点,就是Java编译器不会检查这类异常。也就是说,当程序逻辑出现这类异常时,即使没有用try-catch语句捕获用throws子句声明抛出它,也能通过编译。

Exception类还包含了非运行时异常,也就是除RuntimeException之外的异常,类型上都属于Exception及其子类。从程序语法角度是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自己定义的Exception异常,一般情况下不自定义检查异常

Error(错误)

Error指的是程序无法处理的错误,表示运行应用程序中较严重的问题。

大多数Error与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题。例如VirtualMachineError(Java虚拟机运行错误)、OutOfMemoryError(内存溢出)等问题。当这些异常发生时,JVM一般会选择终止线程。

因为这些错误表示故障发生在虚拟机本身或者虚拟机试图执行应用时,这些错误是无法在编译期发现的,是不可查的,因为它们在应用程序的充值和处理能力之外,而且绝大多数是程序运行时不允许出现的情况。当出现这些情况,在本质上我们也不应该去处理它所引起的异常状况,因此Java通过Error的子类对这些情况进行描述,而非去处理它。

Exception和Error

从上边我们可以了解到,Exception是能被程序本身处理的,Error是无法处理的。Exception更多的是由程序逻辑引起的,是人为可控的,而Error多数是由运行环境引起的,属于客观因素。因此针对可查性,我们可以将Java中的异常分为可查异常和不可查异常。

可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)错误(Error)。

Java中的处理异常机制

在Java应用程序中,异常处理机制为:抛出异常,捕捉异常。

抛出异常

当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的throw语句抛出异常。从方法中抛出的任何异常都必须使用throws子句。

(1)throws抛出异常

throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有异常。多个异常可以用逗号分隔,throws语句的格式如下:

public void methodname throws Exception1,Exception2,...,ExceptionN{
    //方法体
}

方法名后的throws Exception1,Exception2,...,ExceptionN 为声明要抛出的异常列表。当方法抛出异常列表的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理(即谁调用这个方法就谁去处理throws出的异常)。在将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。

Throws抛出异常有以下规则:

  1. 如果是不可查异常,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
  2. 必须声明方法可抛出的任何可查异常。即如果一个方法可能出现可查异常,要么捕获,要么抛出,否则会有编译错误。
  3. 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。如果该方法的调用者没有能力处理该异常的时候,应该继续抛出。
  4. 调用方法必须遵循任何可查异常的处理和声明规则,若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

例如:

void method1() throws IOException{}  //合法    
   
//编译错误,必须捕获或声明抛出IOException    
void method2(){    
  method1();    
}    
   
//合法,声明抛出IOException    
void method3()throws IOException {    
  method1();    
}    
   
//合法,声明抛出Exception,IOException是Exception的子类    
void method4()throws Exception {    
  method1();    
}    
   
//合法,捕获IOException    
void method5(){    
 try{    
    method1();    
 }catch(IOException e){…}    
}    
   
//编译错误,必须捕获或声明抛出Exception    
void method6(){    
  try{    
    method1();    
  }catch(IOException e){throw new Exception();}    
}    
   
//合法,声明抛出Exception    
void method7()throws Exception{    
 try{    
  method1();    
 }catch(IOException e){throw new Exception();}    
}   

(2)throw抛出异常

throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行补刀,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。throw格式如下:

//必须抛出一个Throwable类型的异常
throw new exceptionName;

 

捕获异常

在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

try-catch-finally是Java捕获异常最常用的方式。try-catch-finally语句捕获异常的格式如下:

try {  
    // 可能会发生异常的程序代码  
} catch (Type1 id1){  
    // 捕获并处置try抛出的异常类型Type1  
}  
catch (Type2 id2){  
     //捕获并处置try抛出的异常类型Type2  
} 
finally{
    //无论是否有异常都执行的程序代码
}

try块也称为监控区域,Java方法在运行过程出现异常,则创建异常对象。将异常抛出监控区域之外,由Java运行时系统试图寻找匹配的catch子句以捕获异常。若有匹配的catch子句(匹配的原则是:如果抛出的异常对象属于catch子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与catch块捕获的异常类型相匹配),则运行其异常处理代码,try-catch语句结束。finally块无论是否捕捉或处理异常,finally块里的语句都会被执行。除了一下四种特殊情况不会执行finally语句:

  1. 在finally语句中发生了异常。
  2. 在前面的代码中用了System.exit()退出程序。
  3. 程序所在的线程死亡。
  4. 关闭cpu。

(1)try-catch-finally的语法规则:

  1. 必须在try之后添加catch或finally块,不可单独写一个try块。
  2. 必须遵循try-catch-finally的顺序编写代码,不能更改顺序。
  3. 多个catch块的顺序应该按照异常的范围从小到大编写。
  4. try-catch-finally可以嵌套使用。
  5. 当有多个catch块时,则执行第一个匹配块。即Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,不会再执行其他的 catch代码块。
  6. 在try-catch-finally结构中可以重写抛出异常。
  7. 当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。

(2)try-catch-finally语句块的执行顺序

 

  1. 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
  2. 当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
  3. 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类只需要继承Exception类即可,格式如下:

public class MyException extends Exception{

}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值