java exception的使用规范


合理使用JAVA异常机制可以使程序健壮而清晰,但不幸的是,JAVA异常处理机制常常被错误的使用,下面就是一些关于Exception的注意事项:

1. 原则:不要忽略checked Exception

请看下面的代码:
try
{
    method1();  //method1抛出ExceptionA
}
catch(ExceptionA e)
{
    e.printStackTrace();
}

由于这里的操作已经发生异常,将会导致以后的操作并不能按照预期的情况发展下去,可能导致两个结果:

一是由于这里的异常导致在程序中别的地方抛出一个异常,这种情况会使程序员在调试时感到迷惑,因为新的异常抛出的地方并不是程序真正发生问题的地方,也不是发生问题的真正原因;

另外一个是程序继续运行,并得出一个错误的输出结果,这种问题更加难以捕捉,因为很可能把它当成一个正确的输出。

那么应该如何处理呢,这里有四个选择:

(1)处理异常,进行修复以让程序继续执行。例如在进行数据库查询时,数据库连接断链后重建链接成功。

(2)在对异常进行分析后发现这里不能处理它,那么重新抛出异常,让调用者处理。异常依次向上抛出,如果所有方法都不能恰当地处理异常,最终会在用户界面以恰当的方式提示用户,由用户来判断下一步处理措施。例如在进行数据库查询时,断链后重试几次依然失败的情况。

(3)将异常转换为其他异常再抛出,这时应该注意不要丢失原始异常信息。这种情况一般用于将底层异常封装为应用层异常。

(4)不要捕获异常,直接在函数定义中使用throws声明将抛出该异常。让调用者去处理该异常。

因此,当捕获一个checked Exception的时候,必须对异常进行处理;如果认为不必要在这里作处理,就不要捕获该异常,在方法体中声明方法抛出异常,由上层调用者来处理该异常。


2. 建议:不要捕获unchecked Exception


有两种unchecked Exception:

Error:这种情况属于JVM发生了不可恢复的故障,例如内存溢出,无法处理。

RuntimeException:这种情况属于错误的编码导致的,出现异常后需要修改代码才能修复,一般来说catch后没有恰当的处理方式,因此不应该捕获。(该规则有例外情况,参见:5.11守护线程中需要catch runtime exception)

例如由于编码错误,下面的代码会产生ArrayIndexOutofBoundException。修改代码后才能修复该异常。

int[] intArray = new int[10];

intArray[10]=1;


3. 原则:不要一次捕获所有的异常

请看下面的代码:

try
{
   method1();  //method1抛出ExceptionA
   method2();  //method1抛出ExceptionB
   method3();  //method1抛出ExceptionC
}
catch(Exception e)
{
   ……
}

这是一个很诱人的方案,代码中使用一个catch子句捕获了所有异常,看上去完美而且简洁,事实上很多代码也是这样写的。但这里有两个潜在的缺陷,一是针对try块中抛出的每种Exception,很可能需要不同的处理和恢复措施,而由于这里只有一个catch块,分别处理就不能实现。二是try块中还可能抛出RuntimeException,代码中捕获了所有可能抛出的RuntimeException而没有作任何处理,掩盖了编程的错误,会导致程序难以调试。

下面是改正后的正确代码:


try
{
  method1();  //method1抛出ExceptionA
   method2();  //method1抛出ExceptionB
   method3();  //method1抛出ExceptionC
}
catch(ExceptionA e)
{
   ……
}
catch(ExceptionB e)
{
   ……
}
catch(ExceptionC e)
{
   ……
}


4  原则:使用finally块释放资源

什么是资源:程序中使用的数量有限的对象,或者只能独占式访问的对象。例如:文件、线程、线程池、数据库连接、ftp连接。因为资源是“有限的”,因此资源使用后必须释放,以避免程序中的资源被耗尽,影响程序运行。某些资源,使用完毕后会自动释放,如线程。某些资源则需要显示释放,如数据库连接。

   finally关键字保证无论程序使用任何方式离开try块,finally中的语句都会被执行。在以下情况下,finally块的代码都会执行:

(1) try块中的代码正常执行完毕。

(2)在try块中抛出异常。

(3)在try块中执行return、break、continue。

(4) catch块中代码执行完毕。

(5)在catch块中抛出异常。

(6)在catch块中执行return、break、continue。

因此,当你需要一个地方来执行在任何情况下都必须执行的代码时,就可以将这些代码放入finally块中。当你的程序中使用了资源,如数据库连接,文件,Ftp连接,线程等,必须将释放这些资源的代码写入finally块中。

finally关键字可和catch关键字一起使用。如下:

try
{
   ……
}
catch(MyException e)
{
   ……
}
finally
{
   ……
}

finally关键字也可以单独使用,不catch异常,将异常throw给调用者处理。
try
{
   ……
}
finally
{
   ……
}

5.    原则:finally块不能抛出异常

JAVA异常处理机制保证无论在任何情况下都先执行finally块的代码,然后再离开整个try,catch,finally块。在try,catch块中向外抛出异常的时候,JAVA虚拟机先转到finally块执行finally块中的代码,finally块执行完毕后,再将异常抛出。但如果在finally块中抛出异常,try,catch块的异常就不能抛出,外部捕捉到的异常就是finally块中的异常信息,而try,catch块中发生的真正的异常堆栈信息则丢失了。

请看下面的代码:

 Connection con = null;
try
{
   con = dataSource.getConnection();
   ……
}
catch(SQLException e)
{
   ……
   throw e;//进行一些处理后再将数据库异常抛出给调用者处理
}
finally
{
   try
    {
       con.close();
    }
   catch(SQLException e)
   {
        e.printStackTrace();
        ……
   }
}

运行程序后,调用者得到的信息如下

java.lang.NullPointerException

 atmyPackage.MyClass.method1(methodl.java:266)

而不是我们期望得到的数据库异常。这是因为这里的con是null的关系,在finally语句中抛出了NullPointerException,在finally块中增加对con是否为null的判断可以避免产生这种情况。

6.  原则:异常不能影响对象的状态

异常产生后不能影响对象的状态,这是异常处理中的一条重要规则。在一个函数

中发生异常后,对象的状态应该和调用这个函数之前保持一致,以确保对象处于正确的状态中。

如果对象是不可变对象(不可变对象指调用构造函数创建后就不能改变的对象,即

    创建后没有任何方法可以改变对象的状态),那么异常发生后对象状态肯定不会改变。如果是可变对象,必须在编程中注意保证异常不会影响对象状态。有三个方法可以达到这个目的:

(1)将可能产生异常的代码和改变对象状态的代码分开,先执行可能产生异常的代码,如果产生异常,就不执行改变对象状态的代码。

(2)对不容易分离产生异常代码和改变对象状态代码的方法,定义一个recover方法,在异常产生后调用recover方法修复被改变的类变量,恢复方法调用前的类状态。

(3)在方法中使用对象的拷贝,这样当异常发生后,被影响的只是拷贝,对象本身不会受到影响。

7. 原则:抛出自定义异常异常时带上原始异常信息

请看下面的代码:


public void method2()
{
    try
    {
         ……
        method1();  //method1进行了数据库操作
    } 
    catch(SQLExceptione)
   {
         ……
        throw new MyException(“发生了数据库异常:”+e.getMessage);
    }
}
public void method3()
{
    try
    {
        method2();
    }
    catch(MyExceptione)
    {
        e.printStackTrace();
          ……
    }
}

上面method2的代码中,try块捕获method1抛出的数据库异常SQLException后,抛出了新的自定义异常MyException。这段代码是否并没有什么问题,但看一下控制台的输出:

MyException:发生了数据库异常:对象名称'MyTable' 无效。

at MyClass.method2(MyClass.java:232)

at MyClass.method3(MyClass.java:255)

原始异常SQLException的信息丢失了,这里只能看到method2里面定义的MyException的堆栈情况;而method1中发生的数据库异常的堆栈则看不到,如何排错呢,只有在method1的代码行中一行行去寻找数据库操作语句了,祈祷method1的方法体短一些吧。

JDK的开发者们也意识到了这个情况,在JDK1.4.1中,Throwable类增加了两个构造方法,publicThrowable(Throwable cause)和public Throwable(String message,Throwable cause),在构造函数中传入的原始异常堆栈信息将会在printStackTrace方法中打印出来。


8.    原则:打印异常信息时带上异常堆栈


为便于分析异常原因,在进行异常输出时需要带上异常的堆栈,在进行编码时该问题容易忽视,需要注意,如下:

public void method3()
{
    try
    {
         method2();
    }
    catch(MyExceptione)
   {
              ……//对异常进行处理
             System.out.println(e);//打印异常信息
   }
}

System.out.println(e)相当于System.out.println(e.toString()),不能打印异常堆栈,不利于事后对异常进行分析。

 正确的打印方式:

public void method3()
{
    try
    {
         method2();
    }
    catch(MyExceptione)
    {
             ……//对异常进行处理
              e.printStackTrace();//打印异常信息
    }
}

9. 原则:不要使用同时使用异常机制和返回值来进行异常处理

请看下面一段代码
try
{
    doSomething();
}
catch(MyException e)
{
    if(e.getErrcode == -1)
    {
       ……
    }
    if(e.getErrcode == -2)
    {
       ……
    }
    ……
}

假如在过一段时间后来看这段代码,你能弄明白是什么意思吗?混合使用JAVA异常处理机制和返回值使程序的异常处理部分变得“丑陋不堪”,并难以理解。如果有多种不同的异常情况,就定义多种不同的异常,而不要像上面代码那样综合使用Exception和返回值。

修改后的正确代码如下:

try
{
   doSomething();  //抛出MyExceptionA和MyExceptionB
}
catch(MyExceptionA e)
{
    ……
}
catch(MyExceptionB e)
{
   ……
}

10.  建议:不要让try块过于庞大

出于省事的目的,很多人习惯于用一个庞大的try块包含所有可能产生异常的代码,

这样有两个坏处:

阅读代码的时候,在try块冗长的代码中,不容易知道到底是哪些代码会抛出哪些异常,不利于代码维护。

使用try捕获异常是以程序执行效率为代价的,将不需要捕获异常的代码包含在try块中,影响了代码执行的效率。


11. 原则:守护线程中需要catch runtime exception

守护线程是指在需要长时间运行的线程,其生命周期一般和整个程序的时间一样长,用于提供某种持续的服务。例如服务器的告警定时同步线程,客户端的告警分发线程。由于守护线程需要长时间提供服务,因此需要对runtime exception进行保护,避免因为某一次偶发的异常而导致线程被终止。

while (true)
{
    try
    {
         doSomethingRepeted();
    }
    catch(MyExceptionA e)
    {
        //对checkedexception进行恰当的处理
        ……
    }
    catch(RuntimeException e)
    {
         //打印运行期异常,用于分析并修改代码
         e.printStackTrace();
    }
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值