在理想状态下,程序会按照我们预想的步骤一步一步的执行,但是即使你是大V,你也不可避免出错,所以java为我们提供了异常机制。本文将会从以下几个方面介绍java中的异常机制:
- 异常机制的层次结构
- 异常的处理过程
- 抛出异常
- 捕获异常
- 异常机制的实现细节
一、异常机制的层次结构
在java程序设计语言中,所有的异常对象都是派生于Throwable类,一般情况下,如果java中内置的异常类不能满足需求,可以自定义异常类只需要继承与Throwable类即可,下面是java 中的异常层次结构:
error类表示java内部错误,例如jvm出错或者内存不足等,这层一般不用我们关心(其实我们也无可奈何,如果出现此种问题),一旦出现此种错误程序会自动结束。
我们主要还是需要研究Exception类及其子类,Exception主要有两个子类,IOException和RuntimeException我们一个个看。IOException是IO错误类问题导致的异常,是可预知的,例如由于文件不存在而打开文件失败引起的异常等。RuntimeException类异常表示运行时异常,例如数组下标越界,访问空指针等,是不可预知的,但是这类异常于编写程序时应当予以避免,例如你可以使用if(a==null)…..,加以检查避免。
以上从一种角度对这些主要的异常类进行了分类,其实我们还有一种分类方式,按照是否是检查类异常(checked)进行分类,什么是检查类异常?检查类异常(checked)就是指编译器会检查当前的代码块中,判断是否有这么一条语句在程序执行时可能产生异常,如果有就会建议程序员处理。(实际上你必须处理,不然编译不会通过),在以上的分层中,error类和RuntimeException类属于非检查异常类(unchecked),而IOException属于检查类异常。
二、异常的处理过程
public class Test2 {
public static void main(String[] args){
doMaths();
}
public static void doMaths(){
int a = 10/0;//此处将会出现异常
System.out.println(a);
}
}
输出结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at test.Test2.doMaths(Test2.java:8)
at test.Test2.main(Test2.java:5
相信大家在看完上文之后,可以判断出此处的异常应该属于RuntimeException,属于unchecked异常,由输出的结果可以看出:main方法中调用方法doMaths();,于是进入该方法内部,执行int a = 10/0;产生异常,在本方法中未找到处理,于是返回调用处(即main函数中),发现main函数中依然没有处理方法,于是执行默认操作(打印异常信息)。
我们的程序难免会出现问题,但是绝对不能让用户看出来你的程序出现问题,所以这些异常信息是绝对不能让用户看见的,且不说他是否能看懂,一旦遇到“行家”,你的程序就会存在安全隐患,由此可见,对异常的处理是多么重要。下面我们将讨论两种处理异常的机制。
三、抛出异常
以下所讨论的处理异常的方法,主要还是针对检查类异常(checked),因为对于error类的错误我们没法处理,对于RuntimeException类异常我们应当尽量在程序中加以避免,所以我们主要还是对检查类异常进行处理。
我们使用关键字throws将本程序中可能遇到的异常向上抛出(向调用出抛出,让调用者处理),而本身不做处理。
public void test() throws AppException{
................
...............
}
throws 紧跟方法的括号后面,这个叫异常的声明表示本方法不处理这个异常,谁调用我这个方法谁来处理(后面将讨论如何处理异常,因为总要有人来处理,否则就默认打印异常信息),可以声明多个异常,异常之间使用逗号相隔。
那么throw关键字和throws关键字究竟有什么联系呢?throw可以理解为是在throws关键字的主动行为。先看代码:
public class Test2 {
public static void main(String[] args){
show();
}
public static void show(){
String s = null;
s.charAt(0);
}
}
/*以上代码抛出NullPointerException,空指针异常*/
public static void show() throws NullPointerException{
String s = null;
s.charAt(0);
}
/*你可以选择抛给调用者*/
public static void show(){
String s = null;
if(s==null){
throw new NullPointerException();
}
s.charAt(0);
}
/但是如果你可以判断s处可能出现异常,就可以主动抛出异常*/
小结一下,throws关键字表示:本函数中存在某个异常但是我不知道,如果出现此异常就抛给调用者。throw一般和判断语句if配合的多表示:如果满足条件就结束此函数并向调用者抛出异常,否则就继续执行接下来的代码。
四、捕获异常
以上说了那么多,我们知道如何将遇到的异常抛出,让别人处理,但是总是有人需要处理这个异常的,总不能让系统默认的将异常信息打印出来吧!再者说,一个strong的程序必须要有对错误的处理,让程序控制在手中而不是莫名卡死或者退出。接下来,我们一起探讨如何捕获异常对他进行处理。
捕获异常,我们使用try{}catch{}关键字,try中代码表示可能出现异常的代码块,catch中的代码块表示捕捉到该异常之后需要进行的后续操作。
/*使用上述代码,添加try catch捕获异常*/
public class Test2 {
public static void main(String[] args){
try{
show();//可能出现异常的代码块
}catch(NullPointerException z){
System.out.println("throw Exception");
//捕捉到异常后的操作
}
}
public static void show()throws NullPointerException {
//将异常抛给调用者
String s = null;
s.charAt(0);
}
}
输出结果:
throw Exception
对比于之前,打印自己控制的内容是不是比默认打印异常信息更加的友好?我们常常会遇到这么一个问题:打开一个文件,准备对文件进行操作,但是在对文件的操作过程中出现异常,退出程序了,但是文件并没有被显式的关闭,造成资源浪费。
我们常常使用关键字finally来完成类似这样的操作。finally关键字紧跟catch后面,表示无论是否发生异常都会执行的代码块。finally不是必须的可以没有。
try{
//可能出现异常的代码块
}catch{
//捕获异常之后的代码块
}finally{
//肯定要执行的代码块
}
五、异常机制的实现细节
第一个我想说的是try{}catch{}这种结构并不是唯一的,可以由多个catch语句的,对多个不同的异常进行捕获。
第二个很重要的finally这个关键字中如果出现return等关键字程序的接下来的执行顺序问题。例如:
try{
int a=0;
//出现异常
return a;
}catch(..){
a=1;
return a;
}finally{
a=2;
return a;
}
程序返回:2;
能想明白么?其实不难,只要知道finally语句块中是最后执行的就可以理解了,当try中出现异常,被catch接住,将a赋值为1,在返回之前跳转到finally语句块中,最后在finally语句块中返回2,这种在finally语句块中有return语句的会覆盖catch中的返回。
所以,我们建议finally语句块中不要使用return语句。
以上便是java异常机制的基本内容,如果错误,望大家指出。tk