5.异常处理的陷阱


日常开发中我们无法回避对异常的处理,Java的异常处理有一些令人模糊的地方,详情见下:

正确关闭资源

在实际的开发中,经常需要在程序中打开一些物理资源,如数据库连接,网络连接,磁盘文件等,打开这些物理资源后必须显示关闭资源,否则将造成资源泄漏

finally
{
   if(oos 1= null)
     {
         try{
                os.close();
             }
         catch (Excaption ex)
               {
                  ex.printStackTrace();
               }
        }
    if(ols != null)
       {
          ols.close();
        }
      catch(Exception ex)
        {
           ex.printstacktrace();
         }
   }
}

上面程序所示的资源关闭方式才是比较安全的,这种关闭方式主要保证如下三点。
>使用finally块来关闭物理资源,保证关闭操作总是会被执行。
>关闭每个资源之前首先保证引用该资源的引用变量不为nul。

>为每个物理资源使用单独的try/.catch块来关闭资源,保证关闭资源时引发的异常不会影响其他资源的关闭。


使用Java7增强的try语句关闭资源

使用上面的方式来关闭资源,必然导致finally块代码十分臃肿,这样的代码必将导致程序的可读性降低

方式:它允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源

细节:此处的资源指的是那些必须在程序结束时显式关闭的资源(比如数据库连接、网络连接等),try语句会在该语句结束时自动关闭这些资源。

需要指出的是,为了保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable 或Closeable接口,实现这两个接口就必须实现close()方法。

而 Java7几乎把所有的“资源类”(包括文件io的各种类、JDBC编程的Conmection、Statement等接口…)进行了改写,改写后资源类都实现了AutoCloseable;或Closeable接口。

自动关闭资源的try语句相当于包含了隐式的fmally块(这个finally块用于关闭资源),因此这个try语句可以既没有catch块,也没有finally块。
需要指出的是,使用自动关闭资源的try语句有两个注意点:
》被自动关闭的资源必须实现Closeable或AutoClosable接口。
》被自动关闭的资源必须放在try 语句后的圆括号中声明、初始化。
如果程序需要,自动关闭资源的try 语句后 也可以带多个catch块和一个finally块。


finally块的陷阱

当程序中的try块代码里增加了System.exit(0)来退出程序

结果:程序并不会执行finally块

原理上是:不论try块是正常结束,还是中途非正常退出,finally块确实都会执行。然而在这个程序中,try语句块根本就没有结束其执行过程,System.exit(0);将停止当前线程和所有其他当场死亡的线程。finally块并不能让已经停止的线程继续执行。

原理:{

当System.exit(0)被调用时,虚拟机退出 要执行两项清理工作。
执行系统中注册的所有关闭钩子
}
》如果程序调用了Systen.runFinaizcef0nExit(true),那么JVM会对所有还未结束的对象调用Finalizer

 第二种方式已经被证明是极度危险的,因此JDKAPI文档中说明第二种方式已经过时了,因此实际开发中不应该使用这种危险行为。
第一种方式则是一种安全的操作,程序可以将关闭资源的操作注册成为关闭钩子。在JVM退出之前,这些关闭钩子将会被调用,从而保证物理资源被正常关闭。
具体实例:

public class ExitHook
{
    public static void main(String[]args)
        throws IOBxception
     {
           final FileOutputStream fos;
           fos=new FileOutputStream("a.bin");
           System.out.print1n("程序打开物理资源!");
           //为系统注册关闭钩子
           Runtime.getRuntime().addshutdownlook(
               new Thread()
             {  
                 public void run()
               {       //使用关闭钩子来关闭资源
                  if(fos!=nul1)
                    {    try
                             {  
                                 fos.close();
                              }
                          catch(Exception ex)
                               {
                                  ex.printStackrrace();
                                }
                    } 
              system.out.print1n(“程序关闭了物理资源!");
              }
      };
     System.exit(0);//将exit放在此处,保证执行关闭钩子在之前
   }

}


finally块和方法返回值

总结上面的,我们发现:只要JVM未结束,不论try块是正常结束,还是中途非正常退出,finally块确实都会执行

当finally语句遇到return时会发生什么?

当Java程序执行try块、catch块时遇到了return 语句,returm语句会导致该方法立即结束。

系统执行完return语句之后,并不会立即结束该方法,而是去寻找该异常处理流程中是否包含finally块.

》如果没有finally块,则方法终止,返回相应的返回值;

》1.果有finally块,系统会立即开始执行finally块—(当是普通的finally语句时)只有当finally块执行完成后,系统才会再次跳回来根据 return语句结束方法。

如果finally块里使用了retum语句来导致方法结束,则finally块已经结束了方法,系统将不会跳回去执行try块、catch块里的任何代码

(特别的,当return只在finally语句里,有异常会立马执行finally块,只有当finally块执行完毕后,才会跳回来抛出异常,而finally块里的return语句会立即结束方法,所以即使try块里有引发中止的异常,JVM也不会去返回去抛出异常,中止)

-------------------------------------------------------------------------------------------


catch块的顺序

进行异常捕捉时,一定要记住先捕捉小异常,再捕捉大异常,即try,catch语句的多个catch块应该先捕捉子类异常,后捕捉父类异常,否则编译器会提示编译错误


不要用catch块代替流程控制

譬如把break写入catch块里


只有catch可能抛出的异常

根据Java语言规范,如果一个catch块试图捕捉一个类型为XxxException的checked异常,那么它对应的try块必须可能抛出XxxException或则它的子类的异常,否则编译器将提示具有编译错误-----但在所有checked异常中,Exception是个异类,无论try块是怎样的代码,catch(Exception ex)总是正确的

Runtime异常和Checked异常

RuntimeException类及其子类的异常,的实例被称为RUntime异常,不是RuntimeException类及其子类的异常被称为checked异常

实际上,如果一段代码可能抛出某个Checked异常(这段代码调用的某个方法、构造器声明抛出了该Checked异常),那么程序必须处理这个Checked异常。对于Checked异常的处理方式有两种。
》当前方法明确知道如何处理该异常,程序应该使用try.catch块来捕捉该异常,然后在对应的catch块中修复该异常。
》当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
总之,程序使用catch块捕捉异常时,其实并不能随心所欲地捕捉所有异常。程序可以在
任意想捕捉的地方捕捉RuntimeException异常,但对于其他Checked异常,只有当try块可能
抛出该异常时(try块中调用的某个方法声明抛出了该Checked异常),catch块才能捕捉该

Checked异常。

做点实际的修复

在catch语句中做实际修复的时候,不要试图重新调用该方法,否则会造成递归调用,栈溢出错误


继承得到的异常

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

------------------------------------------------------------------------------------------------


Java7增强的throw语句

在Java7以前,Java编译器的处理“简单而粗暴”:由于你在捕捉该异常时声明ex的类是Exception,因此Java编译器认为这段代码可能抛出Exception异常,因此包含这段代码方法通常需要声明抛出Exception异常

从Java7开始,Java编译器会执行更细致的检查,Java编译器会检查throw语句抛出的异常的实际类型,这样编译器就会知道①号代码实际上只可能抛出FileNotFoundException异常,因此在方法签名中只要声明抛出FileNotFoundException异常即可。即代码可以改为如下形式。

public class ThrowTest2
{
    public static void main(String[]args)
      //Java7会检查①号代码可能抛出异常的实际类型
      //因此该方法只需声明抛出FileNotFoundException异常即可
      throws FileNotFoundExcoption{
   try{
    new FileoutputStream("a.txt");
   }
catch(Exception ex)
{
ex.printStackrrace();
throw ex;
     }
  }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值