java的异常处理机制可以使程序有极好的容错性,让程序更加的健壮.所谓的异常,就是指的阻止当前方法或作用域继续执行的问题,,当程序运行时出现异常时,系统就会自动生成一个Exception对象来通知程序.这样就极大的简化了我们的工作.
当然java的异常对象有很多种,下面这幅图显示了java异常类的继承体系.
从图片中可以看到java将所有的非正常情况分成了两种: 异常(Exception)和错误(Error),他们都继承Throwable父类.Error错误一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误等.这种错误无法恢复或不可捕获,将导致应用程序中断,通常应用程序无法处理这些错误,因此也不应该试图用catch来进行捕获.而对于Exception我们是可以进行捕获并处理的.下面从几个方面对异常处理机制进行介绍.
一.异常处理的一般格式:
1 try{ 2 ///可能会抛出异常的代码 3 } 4 catch(Type1 id1){ 5 //处理Type1类型异常的代码 6 } 7 catch(Type2 id2){ 8 ///处理type2类型异常的代码 9 }
try块中放置可能会发生异常的代码(但是我们不知道具体是哪种异常).如果异常发生了,try块抛出系统自动生成的异常对象,然后异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序,然后进行catch语句执行(不会在向下查找).看起来和switch语句比较类似.除了上面列出的那些异常类,其实我们也可以自己定义异常,然后处理他们.定义异常最重要的是异常类的名字,最好做到望名知义.另外异常处理中经常出现的throws和throw这两个关键字;throws表示的是当前程序不处理这个异常将其抛给调用这个程序的上一级程序执行,如果是main()方法则传给JVM虚拟机处理.throw表示的是显式的抛出一个异常对象.下面的代码中用到了上面提到的知识点.
1 ///自己定义一个简单的异常类,只需要声明以下继承关系 2 ///也可以自己编写构造器,但这不是必要的 3 public class SimpleException extends Exception{ 4 ///public SimpleException(); 5 ///public SimpleException(String str){ 6 ///super(str)}; 7 } 8 9 10 package lkl; 11 12 import java.util.Scanner; 13 14 15 public class ExceptionTest { 16 17 ///声明该程序不处理SimpleException异常而是将其丢出 18 public void f() throws SimpleException{ 19 System.out.println("Throws SimpleException from f()"); 20 ///丢出一个SimpleException异常对象 21 throw new SimpleException(); 22 } 23 ///多个try语句会依次执行,即使上一个try语句发生异常,下一个try语句任然会执行 24 public static void main(String[] args){ 25 ExceptionTest et =new ExceptionTest(); 26 try{ 27 et.f(); 28 }///处理try中丢出的SimpleException异常 29 catch (SimpleException sn){ 30 System.out.println("Catch it"); 31 //调用异常类的printStackTrace()方法,默认是打印到标准错误流的 32 ///现在我们显示指定输出到标准输出流 33 ///会打印从方法调用处直到异常抛出处的方法调用序列,栈顶是最后一次调用的而栈底是第一次掉用的 34 sn.printStackTrace(System.out); 35 } 36 try{ 37 int[] a= new int[10]; 38 Scanner sc = new Scanner(System.in); 39 System.out.println("请输入k: "); 40 int k=sc.nextInt(); 41 sc.close(); 42 System.out.println(a[k]); 43 } 44 ///处理数组下标越界的异常 45 catch(ArrayIndexOutOfBoundsException ae){ 46 System.out.println("Catch ArrayIndexOutOfBoundsException"); 47 ae.printStackTrace(System.out); 48 } 49 } 50 }
二.打印异常信息
异常类的基类Exception中提供了一组方法用来获取异常的一些信息.所以如果我们获得了一个异常对象,那么我们就可以打印出一些有用的信息,最常用的就是void printStackTrace()这个方法,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每个元素都表示栈中的一帧.元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个异常被创建和抛出之处);他有几个不同的重载版本,可以将信息输出到不同的流中去.下面的代码显示了如何打印基本的异常信息:
1 package lkl; 2 3 ///测试异常类Exception的常用方法 4 public class ExceptionMethods { 5 6 public static void main(String[] args){ 7 try{ 8 throw new Exception("My Exception"); 9 }///通过捕获异常类的基类Exception就可以捕获所有类型的异常 10 catch(Exception e){ 11 System.out.println("Caught Exception"); 12 System.out.println("getMessage(): "+e.getMessage()); 13 System.out.println("getLocalizedMessage(): "+e.getLocalizedMessage()); 14 System.out.println("toString(): "+e.toString()); 15 System.out.println("printStackTrace(): "); 16 e.printStackTrace(System.out); 17 } 18 } 19 }
三.重新抛出异常与异常链
有时候需要将捕获的异常重新抛出,这样在语法上是很简单的.但问题是如果直接抛出的话,这个异常对象中信息将仍然是原来异常对象中的信息.如果我们想要跟新一下这个信息,我们可以调用fillInStackTrace()方法,这个方法返回一个Throwable对象,它是通过将当前调用栈信息填入原来那个对象而得到的.调用fillStackTrace()的那一行就成了异常的新的发生地.如果我们在捕获了异常之后抛出了另外一种异常,那么原来异常发生点的信息会丢失,得到与fillInStackTrace()一样的方法.
所谓的异常链指的是在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息给保留下来.java中的Throwable子类在构造器中都可以接受一个cause(因由)对象作为参数.这个参数其实就是原先的异常对象,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也可以通过异常链追踪到异常最初发生的位置.但是在Throwable的子类中,只对Error,Exception,RuntimeException这三个子类提供了带cause因子的构造器,如果要把其它类型的异常链接起来,就应该使用initCause()方法而不是构造器了.
四.使用finally进行清理
引入finally语句的原因是我们希望一些代码总是能得到执行,无论try块中是否抛出异常.这样异常处理的基本格式变成了下面这样:
1 try{ 2 ///可能会抛出异常的代码 3 } 4 catch(Type1 id1){ 5 //处理Type1类型异常的代码 6 } 7 catch(Type2 id2){ 8 ///处理type2类型异常的代码 9 } 10 finally{ 11 ///总是会执行的代码 12 }
其实catch和finally语句也可以只有一个出现.下面的代码显示了finally语句总能运行
1 package lkl; 2 3 public class FinallyTest extends Exception{ 4 5 public static int count=0; 6 public static void main(String[] args){ 7 while(true){ 8 try{ 9 //无论是不是抛出异常,finally语句都会执行 10 if(count++==0){ 11 throw new MyException(); 12 } 13 System.out.println("No exception"); 14 } 15 catch(MyException me){ 16 System.out.println("MyException"); 17 } 18 finally{ 19 System.out.println("In finally clause"); 20 if(count==2) break; 21 } 22 } 23 } 24 }
下面这份代码说明就算在try中有return语句,finally语句仍会得到执行,看起来就像有多个返回点.
1 package lkl; 2 3 public class MultipleReturns { 4 5 public static void f(int i){ 6 System.out.println("Initialization that requires cleanup"); 7 try{///在函数reutrn之前,finally回先执行 8 System.out.println("Point 1"); 9 if(i==1) return ; 10 System.out.println("Point 2"); 11 if(i==2) return ; 12 System.out.println("Point 3"); 13 if(i==3) return ; 14 System.out.println("Point 4"); 15 if(i==4) return; 16 } 17 ///在上面的return之前,会先执行finally语句 18 finally{ 19 System.out.println("Perferming cleanup"); 20 } 21 } 22 public static void main(String[] args){ 23 for(int i=1;i<=4;i++) 24 f(i); 25 } 26 }
那么总是会执行的finally语句用来干嘛?在java中我们主要用来清理除内存外的其它一些资源,这些资源包括:已经打开的文件或网络连接,在屏幕上画的图像等.
五.异常处理与继承
最后一个问题是关于异常处理和继承的.当异常处理和继承一起发生的时候,我们需要关注的问题就是父类构造器抛出异常和子类构造器抛出异常的关系,在子类中重写父类方法是怎样声明异常等问题.总结一下其实也就是二个规则:
1).子类重写基类方法抛出的异常必须不比原基类方法抛出的异常类型更大.
2).虽然在语法上对子类构造器的异常声明没有规定,但是子类构造器的异常说明必须包括基类构造器的异常说明.(因为基类的构造器总是会被调用).
下面的代码对此进行了演示:
1 public class Foul extends BaseballException{ 2 } 3 4 public class PopFoul extends Foul{ 5 } 6 7 public class BaseballException extends Exception{ 8 } 9 10 public class Strike extends BaseballException{ 11 12 } 13 14 public class RainedOut extends StormException{ 15 16 } 17 18 public interface Storm { 19 20 public void event() throws RainedOut; 21 public void rainHard() throws RainedOut; 22 } 23 24 25 public abstract class Inning { 26 27 public Inning() throws BaseballException{} 28 ///定义抛出异常,但是实际上并没有 29 public void event() throws BaseballException{}; 30 //声明抛出两个异常 31 public abstract void atBat() throws Strike ,Foul; 32 33 public void walk() {} 34 } 35 36 37 38 public class StormyInning extends Inning implements Storm{ 39 40 public StormyInning() throws BaseballException,RainedOut{} 41 public StormyInning(String s) 42 throws Foul,BaseballException{} 43 //因为基类方法中没有抛出异常而子类覆盖后的方法抛出了 44 ///所以编译报错 45 //void walk() throws PopFoul{} 46 47 //接口不能给基类方法添加异常类型 48 //public void event() throws RainedOut{} 49 50 ///重写的方法抛出和基类方法一样的异常是可以的 51 public void rainHard() throws RainedOut{} 52 53 //基类的方法抛出了异常,而重写的方法不抛出也是可以的 54 public void event() {} 55 56 //重写的方法抛出基类异常的子类也是可以的 57 public void atBat() throws PopFoul{} 58 59 public static void main(String[] args) { 60 try{///下面的两句代码中构造器中可能抛出RainedOut,BaseballException异常 61 ///atBat()方法可能抛出PopFoul异常.所以需要处理三种异常 62 StormyInning si = new StormyInning(); 63 si.atBat(); 64 } 65 catch(PopFoul e){ 66 System.out.println("Pop Foul"); 67 } 68 catch(RainedOut e){ 69 System.out.println("Rained Out"); 70 } 71 catch(BaseballException e){ 72 System.out.println("Generic baseball"); 73 } 74 try{//这段代码进行了向上转型,所以处理上面的三种情况外 75 ///还必须处理基类atBat()方法抛出的Strike异常 76 Inning i = new StormyInning(); 77 i.atBat(); 78 } 79 catch(Strike e){ 80 System.out.println("Strike"); 81 } 82 catch(PopFoul e){ 83 System.out.println("Pop Foul"); 84 } 85 catch(RainedOut e){ 86 System.out.println("Rained Out"); 87 } 88 catch(BaseballException e){ 89 System.out.println("Generic baseball"); 90 } 91 } 92 }