java异常捕捉陷阱(内存泄漏,finally块,catch块,继承得到的异

1. 异常捕捉的陷阱

异常处理机制是java语言的特色之一,尤其是java语言的Checked异常,更是体现了java语言的严谨性:没有完善错误处理的代码根本不会被执行。对于Checked异常,java程序要么声明抛出,要么使用try……catch进行捕获。

1.1 正确关闭资源的方式

在实际开发中,经常需要在程序中打开一些物理资源,如数据库连接,网络连接,磁盘文件等,打开这些物理资源之后必须显示关闭,否则将会导致资源泄漏。因为垃圾回收机制属于java内存管理的一部分,它只是负责会受堆内存中分配出来的内存,至于程序中打开的物理资源,垃圾回收机制是无能为力的。

为了正常关闭程序中打开的物理资源,应该使用finally块来保证回收。比如下面三种关闭资源的方式,哪种更好些?

[java] view plaincopyprint?
    publicstaticvoidmain(Stringargs[])throwsException{Studentstudent_new=newStudent("liyafang");Studentstudent_recover=null;ObjectOutputStreamoos=null;ObjectInputStreamois=null;try{oos=newObjectOutputStream(newFileOutputStream("liyafang.txt"));ois=newObjectInputStream(newFileInputStream("liyafang.txt"));oos.writeObject(student_new);oos.flush();student_recover=(Student)ois.readObject();System.out.println(student_recover.equals(student_new));System.out.println(student_recover==student_new);}finally{//1.第一种关闭资源的方式(不够安全):程序刚开始指定oos=null;ois=null;完全有可能在程序运行过程中初始化oos之前就引发了异常,那么oos,ois还没有来得及初始化,因此oos,ois根本无需关闭。oos.close();ois.close();//2.第二种关闭资源的方式(还是不够安全):假如程序开始已经正常初始化了oos,ois两个IO流,在关闭oos是出现了异常,那么程序将在关闭oos时非正常退出,这样就导致ois得不到关闭,从而导致资源泄漏。为了保证关闭各资源时出现的异常不会相互影响,应该在关闭每个资源时分开使用trycatch块来保证关闭操作不会导致程序非正常退出。if(oos!=null){oos.close();}if(ois!=null){ois.close();}//3.第三种关闭资源的方式(比较安全):主要保证一下几点://(1)使用finally块来关闭物理资源,保证关闭操作始终会被执行;//(2)关闭每个资源之前首先保证引用该资源的引用变量不为null;//(3)为每个物理资源使用单独的trycatch块关闭资源,保证关闭资源时引发的异常不会影响其他资源的关闭。if(oos!=null){try{oos.close();}catch(Exceptionex){ex.printStackTrace();}}if(ois!=null){try{ois.close();}catch(Exceptionex){ex.printStackTrace();}}}}
public static void main(String args[]) throws Exception {                   Student student_new = new Student("liyafang");                   Student student_recover = null;                   ObjectOutputStream oos = null;                   ObjectInputStream ois = null;                   try {                            oos = new ObjectOutputStream(new FileOutputStream("liyafang.txt"));                            ois = new ObjectInputStream(new FileInputStream("liyafang.txt"));                            oos.writeObject(student_new);                            oos.flush();                            student_recover = (Student) ois.readObject();                            System.out.println(student_recover.equals(student_new));                            System.out.println(student_recover == student_new);                   } finally {//1.第一种关闭资源的方式(不够安全):程序刚开始指定oos = null;ois = null;完全有可能在程序运行过程中初始化oos之前就引发了异常,那么oos,ois还没有来得及初始化,因此oos,ois根本无需关闭。                            oos.close();                            ois.close();//2.第二种关闭资源的方式(还是不够安全):假如程序开始已经正常初始化了oos,ois两个IO流,在关闭oos是出现了异常,那么程序将在关闭oos时非正常退出,这样就导致ois得不到关闭,从而导致资源泄漏。为了保证关闭各资源时出现的异常不会相互影响,应该在关闭每个资源时分开使用try catch块来保证关闭操作不会导致程序非正常退出。                            if(oos != null){                                     oos.close();                            }                            if(ois != null){                                     ois.close();                            }//3.第三种关闭资源的方式(比较安全):主要保证一下几点://(1)使用finally块来关闭物理资源,保证关闭操作始终会被执行;//(2)关闭每个资源之前首先保证引用该资源的引用变量不为null;//(3)为每个物理资源使用单独的trycatch块关闭资源,保证关闭资源时引发的异常不会影响其他资源的关闭。                            if(oos != null){                                     try{                                               oos.close();                                     }catch (Exception ex){                                               ex.printStackTrace();                                     }                            }                            if(ois != null){                                     try{                                               ois.close();                                     }catch (Exception ex){                                               ex.printStackTrace();                                     }                            }                 }         }

1.2 finally块的陷阱

当程序在finally之前使用System.exit(0),finally将不执行。调用System.exit(0)将使JVM退出,只要JVM不退出,finally就一定会得到执行。

在java程序执行try块、catch块时遇到了return语句,return语句会导致该方法立即结束。系统执行完return语句之后,并不会立即结束该方法,而是去寻找该异常处理流程中是否包含Finally块,如果没有Finally块,方法终止,返回相应的返回值。如果有Finally块,系统立即开始执行Finally块,只有当Finally执行完成后,系统才会再次跳回来根据return语句结束方法。如果Finally块使用了return语句来导致方法的结束,则finally块已经结束了方法,系统不会跳回去执行trycatch里的任何代码。

[java] view plaincopyprint?

    publicinttest(){intcount=1;try{return++count;}finally{return++count;}}//以上代码最终返回值是:3publicinttest(){intcount=1;try{return++count;}finally{returncount++;}}//以上代码最终返回值是:2
public int test(){                   int count = 1;                   try{                            return ++count;                   }finally{                            return ++count;                   }         }//以上代码最终返回值是:3public int test(){                   int count = 1;                   try{                            return ++count;                   }finally{                            return count++;                   }         }//以上代码最终返回值是:2

throw语句的执行和return语句比较类似。当程序执行trycatch块遇到throw语句时,throw语句会导致该方法立即结束,系统执行throw语句时并不会立即抛出异常,而是去寻找该异常处理流程中是否包含finally块。如果没有finally块,程序立即抛出异常。如果有finally块,系统立即执行finally块,只有当finally块执行完成之后,系统才会再次跳出来抛出异常。如果finally块里使用return语句来结束方法,系统将不会跳回去执行try块,catch块去抛出异常。

例如1:

[java] view plaincopyprint?

    intcount=1;try{thrownewRuntimeException("异常1");}finally{returncount++;}//执行结果:返回值是1,同时不会抛出任何异常。
int count = 1;                   try{                            throw new RuntimeException("异常1");                   }finally{                            return count++;                   }//执行结果:返回值是1,同时不会抛出任何异常。

例如2:

[java] view plaincopyprint?

    intcount=1;try{thrownewRuntimeException("异常1");}finally{thrownewRuntimeException("异常2");}//执行结果:Exceptioninthread"main"java.lang.RuntimeException:异常2
int count = 1;                   try{                            throw new RuntimeException("异常1");                   }finally{                            throw new RuntimeException("异常2");                   }//执行结果:Exception in thread "main" java.lang.RuntimeException: 异常2

1.3 catch块的用法1.3.1 catch的顺序

对于java的异常捕获来说,每个try块至少需要一个catch块或一个finally块,绝不能只有单独一个孤零零try块。通常情况下,如果try块被执行一次,则try块后只有一个catch块会被执行,绝不可能有多个catch块被执行。除非在循环中使用了continue开始下一次循环,下一次循环又重新运行了try块,才可能导致多个catch块被执行。由于异常处理机制中排在前面的catch(XxxException ex)块总是会优先获得执行的机会,因此java对try块后的多个catch块的排列顺序是有要求的。

因为java的异常有非常严格的继承体系,许多异常类之间有严格的父子关系,比如程序FileNotFoundException异常就是IOException的子类。捕获父类异常的catch块都应该排在捕捉子类异常的catch块之后【先处理小异常(子类异常),在处理大异常(父类异常)】,否则将出现编译错误。

例如以下两个catch语句不能颠倒顺序:

[java] view plaincopyprint?

    FileInputStreamfis=null;try{fis=newFileInputStream("a.bin");fis.read();}catch(FileNotFoundExceptionex){ex.printStackTrace();}catch(IOExceptione){e.printStackTrace();}
 FileInputStream fis = null;                   try{                            fis = new FileInputStream("a.bin");                            fis.read();                   }catch(FileNotFoundException ex){                            ex.printStackTrace();                   }catch(IOException e){                            e.printStackTrace();                   }

1.3.2不要用catch代替流程控制

如下边这个例子:

[java] view plaincopyprint?

    String[]students={"liyafang","zhoushilong","luorongbo"};inti=0;while(true){try{System.out.println(students[i++]);}catch(IndexOutOfBoundsExceptionex){break;}}
String[] students = {"liyafang","zhoushilong","luorongbo"};                   int i = 0;                   while(true){                            try{                                     System.out.println(students[i++]);                            }catch(IndexOutOfBoundsException ex){                                     break;                            }                   }

这种遍历数组的方式不仅难以阅读,而且运行速度还非常慢。

千万不要使用异常来进行流程控制。异常机制不是为流程控制而准备的,异常机制知识为程序的意外情况准备的,因此程序只应该为异常情况使用异常机制。所以,不要使用这种“别出心裁”的方法来遍历数组。

1.3.3只能catch可能抛出的异常

[java] view plaincopyprint?

    /*publicstaticvoidtest1(){try{System.out.println("something");}catch(IOExceptione){e.printStackTrace();}}publicstaticvoidtest2(){try{System.out.println("something");}catch(ClassNotFoundExceptione){e.printStackTrace();}}*/
    /*public static void test1(){                   try{                            System.out.println("something");                   }catch(IOException e){                            e.printStackTrace();                   }         }         public static void test2(){                   try{                            System.out.println("something");                   }catch(ClassNotFoundException e){                            e.printStackTrace();                   }         }*/

以上代码java编译器是不允许的。

根据java语言规范,如果一个catch子句试图捕获一个类型为XxxException的Checked异常时,那么它对应的try子句必须可能抛出XxxException或其子类的异常,否则编译器将提示该程序具有编译错误—但在所有的Checked异常中,Exception是一个异类,无论try块是怎样的代码,catch(Exception e)总是正确的。

RuntimeException 类及其子类的实例被称为Runtime异常,不是RuntimeException类及其子类的异常实例则被称为Checked异常,只要愿意,程序员总可以使用catch(XxxException ex)来捕获运行时异常。

总之,程序使用catch捕捉异常时,其实并不能随心所欲地捕捉所有异常。程序可以在任意想捕捉的地方捕捉RuntimeException异常,Exception,但对于其他的Checked异常,只有当try块可能抛出该异常时(try块中调用的某个方法声明抛出了该Checked异常),catch块才捕捉该Checked异常。

1.3.4实际的修复

如果程序知道如何修复指定异常,应该在catch块内尽量修复该异常,当该异常情况被修复后可以再次调用该方法;如果程序不知道如何修复该异常,也没有进行任何修复,千万不要再次调用可能导致该异常的方法。

无论如何不要在finally块中递归调用可能引起异常的方法,因为这将导致该方法的异常不能被正常抛出,甚至StackOverflowError错误也不能中止程序,只能采用强行结束java.exe进程的方法来中止程序的运行。

1.4 继承得到的异常

Java语言规定:子类重写父类的方法时,不能声明抛出比父类方法类型更多,范围更大的异常。也就是说,子类重写父类方法时,子类方法只能声明抛出父类方法所声明抛出的异常的子类。

例如:

[java] view plaincopyprint?

    publicinterfaceType1{voidtest()throwsClassNotFoundException;}publicinterfaceType2{voidtest()throwsNoSuchMethodException;}classTestimplementsType1,Type2{@Overridepublicvoidtest(){}}
public interface Type1 {         void test() throws ClassNotFoundException;}public interface Type2 {         void test() throws NoSuchMethodException;}class Test implements Type1, Type2 {         @Override         public void test() {         }}

上面代码的异常处理是正确的。

Test实现了Type1接口,实现Type1接口里的test()方法时可以声明抛出ClassNotFoundException异常或该异常的子类,或者不声明抛出;Test类实现了Type2接口,实现了Type2接口里的test()方法时可以声明抛出NoSuchMethodException异常或该异常的子类,或者不声明抛出。由于Test类同时实现了Type1,Type2两个接口,因此需要同时实现两个接口中的test()方法。只能是上面两种声明抛出的交集,不能声明抛出任何异常。

关于爱情的句子:情不知所起,一往而情深。

java异常捕捉陷阱(内存泄漏,finally块,catch块,继承得到的异

相关文章:

你感兴趣的文章:

标签云: