Logo

郎哥编程

让程序主动抛出异常及异常后续处理

2018-08-05 1224

文章导读

用try—catch语句可以在方法体中直接捕获发生的异常,当try语句捕获到异常后,将进入catch语句处理,但在某些特定的情况下,不管是否有异常发生,总是要求某些特定的代码必须被执行,这种情况如何处理?异常有时需要调用方来处理,需要把方法体中发生的异常抛给调用方,这样的情况又该如何处理?本文将给出上述问题的解决方案,并对异常信息显示及异常分类做了进一步说明。


本文分成三个小节。第一小节介绍finally语句的用法,用finally语句可以确保在异常发生时,某些特定的代码必须被执行;第二小节绍throw和throws语句,用于在方法体中抛出异常给调用方;第三小节绍如何显示异常信息以及异常的分类。

第一小节  Java异常处理语句finally的用法

前面说过,如果try语句块中存在异常,则异常之后的代码将不再执行。但在某些特定的情况下,不管是否有异常发生,总是要求某些特定的代码必须被执行。例如,程序中执行数据库连接的代码,不管对数据库的操作是否成功,最后都要关闭数据库的连接以释放内存资源,这就需要用到finally语句。

finally语句的语法规则如下:

try {

   ……..  //代码段(可能发生异常的代码)

} catch( 异常类型 ex ) {

   ……..  //对异常进行处理的代码

} finally {

   ……..  //总要被执行的代码

}

………   //代码段

下面通过示例演示throws的用法,代码如下:

public class FinallyDemo {
    public static void main(String args[]) {
        System.out.println("请打开数据库连接。。。。。。");
        try {
            System.out.println("执行查询操作");
            System.out.println("执行修改操作");
            int  i= 12/0;
            System.out.println("执行添加操作");
            System.out.println("执行删除操作");
        } catch(Exception ex) {
            System.out.println("除零出错!");
            ex.printStackTrace();
        } finally {
            System.out.println("请关闭数据库连接......");
        }
    }
}

代码为了演示finally语句的效果,在try语句块中设置了除零异常。当代码执行到语句i= 12/0时,程序抛出异常。控制台显示结果如下图所示:

blob.png

图 1  finally示例执行结果

从上面的代码可以看出,不管有没有发生异常,都不会影响到finally语句的执行。只有一种情况会阻止finally语句的执行,就是JVM被关闭。不管try是以何种方式结束的(正常结束、异常结束、通过return或break控制流语句结束),finally语句也总是恰好在成员函数返回前执行。

下面通过示例ReturnExceptionDemo讲解return语句的作用:

public class ReturnExceptionDemo {
    static void methodA() {
          try {
            System.out.println("进入方法A");
            //抛出异常
            throw new RuntimeException("制造异常");
          } finally {
             //该语句不管发生什么都会执行
             System.out.println("执行A方法的finally");
          }
       }
       
       static void methodB() {
              try {
                System.out.println("进入方法B");
                //返回,实际上是在finally语句执行完后才返回
                return;
              } finally {
                 //该语句不管发生什么都会执行
                 System.out.println("执行B方法的finally");
              }
       }
      
       public static void main(String args[]) {
           try {
               methodA();
           } catch( Exception e ) {
               System.out.println(e.getMessage());
           }
           methodB();
       }
 
}

运行程序,输出结果如下图所示:

blob.png


图2 示例ReturnExceptionDemo的输出结果

从输出结果可以看出,不管什么情况下,finally语句都会被执行。

在代码methodB方法try语句块中,程序执行到return语句时,程序会产生一个局部展开,finally语句块的代码会被插入到return语句之前执行。不过注意的是,finally语句块的代码虽然在return语句之前执行,但是finally语句块的代码不能够通过重新赋值的方式来改变return语句的返回值。

看如下的示例代码:

package testfinallyreturn;
public class TestFinallyReturn {
    public static void main(String args[]) {
        //你认为testFinally方法返回的值是多少呢?
        System.out.println("testFinally返回的值为:" + testFinally());
 
    }
   
    public static int testFinally()
    {
        int temp = 1;
        try {
            return temp;
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            temp = 2;
        }
        return 0;
    }
}

例子代码中,本来是想在finally语句块中通过改变temp的值,来影响testFinally方法最终返回值,但是真的影响了吗?程序输出结果如下图所示:

blob.png

图3  TestFinallyReturn输出结果

从上图可以看出,testFinally方法并没有返回我们需要的值。虽然finally语句块的代码不会影响try语句块中return语句的返回值。但是可以在finally内部使用return语句。如果把上列的finally语句块的代码改为如下所示:

    public static int testFinally()
    {
        int temp = 1;
        try {
            return temp;
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            temp = 2;
            return temp;
        }
        return 0;
    }

由于finally语句块的代码优于return语句执行,如果此时在finally语句块中内部也有return语句,这将会导致该方法直接返回,而使try语句块中的return语句得不到执行机会,所以返回结果为2。

对上述情况,其实更合理的做法是,既不在try语句块中使用return语句,也不在finally语句块中使用return语句,而应该在finally语句块之后使用return语句来表示方法的结束和返回。修改后的代码如下:

  public static int testFinally()
    {
        int temp = 1;
        try {
            //操作语句
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            temp = 2;
        }
        return temp;
    }

第二小节  使用throw和throws 引发异常

前面讨论了如何捕获Java程序运行过程中由系统引发的异常,如果想在程序中明确地引发异常,则需要用到throw和throws语句。

1、throw语句

throw语句通常用在方法体中,并且抛出一个异常对象。程序在执行到throw语句时立即停止,它后面的语句都不执行。

throw语句的语法规则如下:

throw ThrowableInstance

其中,ThrowableInstance是Throwable类型或Throwable子类类型的一个对象。通过参数传递到catch子句,或者用new语句来创建一个实例。

下面通过示例演示throw的用法,代码如下:

package com.milihua.throwdemo;
public class ThrowDemo {
    static void demoproc() {
        try {
           throw new NullPointerException("demo");
        } catch(NullPointerException e) {
           System.out.println("Caught inside demoproc.");
           throw e; // rethrow the exception
        }
     }
 
     public static void main(String args[]) {
        try {
           demoproc();
        } catch(NullPointerException e) {
           System.out.println("Recaught: " + e);
        }
     }
 
}

在demoproc()方法的try语句块中,应用throw语句抛出一个NullPointerException异常,该异常被demoproc()方法的catch语句捕获,执行catch语句的代码,首先输出"Caught inside demoproc."语句到控制台,随后再次应用throw语句抛出NullPointerException异常,抛出的异常被main方法的catch语句捕获,输出异常信息。输出结果如下图所示:

blob.png


图 4  TryDemo输出结果

语句“throw new NullPointerException("demo");”,用new来构造一个NullPointerException实例。所有的Java内置的异常类有两个构造函数:一个没有参数,一个带有一个字符串参数。当用到第二种形式时,参数指定描述异常的字符串。如果对象用作 print( )或println( )的参数时,该字符串被显示。也可以通过调用gtMessage( )来实现,getMessage( )是由Throwable定义的。

2、throws语句

如果一个方法可以引发异常,而它本身并不对该异常进行处理,那么该方法必须将这个异常抛给调用者可以使程序能够继续执行下去,这时候就要用到throws语句。

throws语句的语法规则如下:

returnType  methodName()  throws  ExceptionType1, ExceptionType2,….
{
     ……..//方法体
}

在方法体中可以是引发异常列表中的任何一种异常及其子类型的异常。throws用来声明一个方法可能会抛出所有的异常,它跟在方法名称的后面。如果有多个异常,则使用逗号将其分开。调用者调用该方法时,必须在调用处处理这个异常,一般情况下由调用此方法的类来处理。

下面通过示例演示throws的用法,代码如下:

public class testThrows()
{ 
    public static void testdemo() throws NumberFormatException{  
        String s = "abc";  
        System.out.println(Double.parseDouble(s));  
    }  
 
    public static void main(String[] args) {  
        try {  
            function();  
        } catch (NumberFormatException e) {  
            System.err.println("非数据类型不能强制类型转换。");  
            //e.printStackTrace();  
    }  
}

如果方法声明后有throw语句,则在此方法被调用时,需要在调用方法中用try和catch进行异常捕获,如果不捕获异常,则需要在调用方法中使用throws语句将异常抛出。

当覆盖抛出异常的方法时,覆盖方法仅需要声明异常的同类或子类。例如,如果父类方法抛出IOException,则覆盖方法可以抛出IOException、FileNotFoundException(IOException的子类),但不可以抛出Exception(IOException的父类)。

3、throw和throws语句的组合应用

在实际应用中,一般都需要throw和throws语句组合应用,就是在捕获异常后,抛出一个明确的异常给调用者。例如,现在要使用一个相除的方法,但是在操作之前必须打印“运算开始”的信息,结束之后必须打印“异常结束”。

下面通过示例演示throw和throws组合应用的用法,代码如下:

package com.milihua.throwdemo;
public class ThrowAndThrowsDemo {
    public static int div(int i,int j) throws Exception{   
        // 定义除法操作,如果有异常,则交给被调用处处理
        System.out.println("***** 计算开始 *****") ;
        int temp = 0 ;    // 定义局部变量
        try{
         // 计算,但是此处有可能出现异常
            temp = i / j ;   
        }catch(Exception e){
            throw e;
        }finally{   
         // 不管是否有异常,都要执行统一出口
            System.out.println("***** 计算结束 *****") ;
        }
        return temp ;
    }
   
    public static void main(String args[]){
        try{
            System.out.println("除法操作:" + div(10,0)) ;
        }catch(Exception e){
            System.out.println("异常产生:" + e) ;
        }
    }
}

代码中div方法名称后面使用了throws语句抛出Exception异常,main方法是div方法的调用者,因此能够捕获throws语句抛出的异常。finally语句块是不管异常发生与否,都要执行的代码块,下一节会详细讲解。

第三小节  显示异常信息与异常分类

1. 如何显示异常信息

前面介绍了各种异常类及异常语句的用法。在实际编程过程中,还需要显示导致异常出现的信息,方便程序员根据给出的异常信息查找程序错误。异常类提供了输出异常信息的两个方法:getMessage方法和printStackTrace方法。

getMessage方法

用于获取异常的详细消息字符串。

printStackTrace方法

输出Throwable对象的堆栈跟踪信息到控制台。

在catch中声明的异常对象catch(Exception ex)封装了异常事件发生的信息,在catch语句块中可以使用这个对象的getMessage方法获取错误信息。

getMessage的使用方法示例如下:

     public static void main(String[] args) {  
        try {  
            int  i = 12 / 0;  
        } catch (Exception e) {  
            System.out.println(e.getMessage());  
 }

运行代码,因为被零除,所以发生异常,输出“/  by  zero”。

printStackTrace的用法示例如下:
    public static void main(String[] args) {
        try {  
            int  i = 12 / 0;  
        } catch (Exception e) {  
            e.printStackTrace();
        }
    }

运行上述代码,输出结果如下下图所示:

blob.png


图5  printStackTrace输出结果

使用printStackTrace方法可以获取异常的具体类型,这样就可以在使用throw是抛出一个确切的异常。

2. 异常的分类

从编程角度考虑,可以将异常分为以下几种。

(1)非受检异常

非受检异常是指编译器不要求强制处置的异常。一般是指因设计或实现方式不当而导致的问题。也可以说,是程序员的原因导致的,是本来可以避免发生的情况。

java.lang.RuntimeException类及其子类都是非受检异常。具体如下:

●  java.lang.ClassCastException:错误的类型转换异常。

●  java.lang.ArrayIndexOutOfBoundsException:组下标越界异常。

●  java.lang.NullPointException:空指针访问异常。

●  java.lang.ArithmeticException:除零溢出异常。

如果事先检查数组元素下标,保证其不超出数组长度,ArrayIndexOutOfBoundsException异常就不会抛出;再如,先检查并确保一个引用类型变量值不为NULL,然后再访问其属性和方法,那么,NullPointException异常就不会抛出。因此,如果程序设计良好并且正确实现,这类异常就不会发生,所以通常也不会处理这类异常。

(2)受检异常

受检异常是指编译器要求必须处置的异常,即程序在运行时由于外界因素造成的一般性异常,具体如下:

●  java.lang.ClassNotFoundExeption:没有找到具有指定名称的类异常。

●  java.lang.FileNotFoundException:访问不存在的文件异常。

●  java.lang.IO Exception:操作文件时发生的异常。

●  java.sql.SQL Exception:操作数据库时发生的异常。

Java编译器要求Java程序必须捕获或声明所有受检异常。如FileNotFoundException、IO Exception等。因为,对于这类异常来说,如果程序不进行处理,可能会带来意向不到的结果。而非受检异常可以不做处理,因为这类异常很普遍,若全部处理可能会对程序的可读性和运行效率产生影响。

3. 自定义异常类

尽管Java提供了众多的异常类,但程序员有时可能需要定义自己的异常类来处理某些问题。例如,可以抛出中文文字的异常提示信息,帮助客户了解异常产生的原因,在这种情况下用户只要定义一个或直接或间接继承Throwable的类就可以了。一般情况下,自定义的异常类都选择Exception作为父类。直接继承Exception类的异常属于已检查异常,所以必须进行相应的处理。

文章小结

1、finally语句一般和try......catch......连用,用来做一些善后清理工作。可以把文件关闭,关闭数据库连接等操作放置到finally语句块内,避免程序出现异常时,不能及时执行关闭文件等释放系统资源操作。

2、throw语句在方法体中使用,而throws语句是用在方法名称之后的。在同一个方法中使用throw和throws时要注意,throws抛出的类型范围要比throw抛出的对象范围大才可以。

3、对于初学Java开发的读者来说,并不是对所有的方法都要进行异常处理,因为异常处理要占用一定的资源,影响程序的执行效率,在异常处理方面要有所侧重。

思考与练习

编写一个ExceptionTest类,在main方法中使用try、catch、finally语句,要求:

(1)在try语句块中,编写被零除的代码;

(2)在catch语句块中,捕获被零除所产生的异常,并且打印异常信息;

(3)在finally语句块中,打印一条语句。


代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论