前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >​Java中关于try、catch、finally中的细节分析

​Java中关于try、catch、finally中的细节分析

原创
作者头像
weylan
修改2021-10-29 11:18:10
1K0
修改2021-10-29 11:18:10
举报
文章被收录于专栏:小石头记
前言

阿里巴巴开发手册中有这么一条:【强制】不要在 finally 块中使用 return , 在开发过程中发现部分同学对这条规则理解不是很透彻,本文将就 try 、catch、finally 的一些问题,分析一下 try 、catch、finally 的处理流程。

首先看一个例子:

例1
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test()); // print try
    }
}

首先程序执行try语句块,把变量 t 赋值为 try,由于没有发现异常,接下来执行 finally 语句块,把变量 t 赋值为"finally",然后return t,则 t 的值是 "finally",最后 t 的值就是 "finally",程序结果应该显示 "finally",但是实际结果为 "try" 。

为什么会这样,我们不妨先看看这段代码编译出来的class对应的字节码,看虚拟机内部是如何执行的。

首先将类编译成 class

代码语言:txt
复制
javac -g:vars TryCatchFinally.java

显示目标 class 文件的字节码信息

代码语言:txt
复制
 javap -verbose TryCatchFinally 

编译出来的字节码部分,我们只需关注 test 方法,其它先忽略掉。关于jvm虚拟机字节码指令意思,可查阅Java 虚拟机字节码指令表

代码语言:txt
复制
 public static java.lang.String test();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=0
         0: ldc           #2                  // String
         2: astore_0
         3: ldc           #3                  // String try
         5: astore_0
         6: aload_0
         7: astore_1
         8: ldc           #4                  // String finally
        10: astore_0
        11: aload_1
        12: areturn
        13: astore_1
        14: ldc           #6                  // String catch
        16: astore_0
        17: aload_0
        18: astore_2
        19: ldc           #4                  // String finally
        21: astore_0
        22: aload_2
        23: areturn
        24: astore_3
        25: ldc           #4                  // String finally
        27: astore_0
        28: aload_3
        29: athrow
      Exception table:
         from    to  target type
             3     8    13   Class java/lang/Exception
             3     8    24   any
            13    19    24   any
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           14      10     1     e   Ljava/lang/Exception;
            3      27     0     t   Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 13
          locals = [ class java/lang/String ]
          stack = [ class java/lang/Exception ]
        frame_type = 74 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]

首先看LocalVariableTable信息,这里面定义了两个变量 一个是 t String类型,一个是 e Exception 类型

接下来看Code部分

  • 第0-2行,给第一个本地变量赋值"",也就是String t="";
  • 第3-6行,也就是执行try语句块 赋值语句 ,也就是 t = "try";
  • 第7行,重点是第7行,把第s对应的值"try"付给第二个本地变量,但是这里面第二个本地变量并没有定义,这个比较奇怪
  • 第8-10 行,对第一个变量进行赋值操作,也就是t="finally"
  • 第11-12行,把第二个变量对应的值返回

通过字节码,我们发现,在 try 语句的 return 块中,return 返回的引用变量( t 是引用类型)并不是try语句外定义的引用变量t,而是系统重新定义了一个局部引用 t ’,这个引用指向了引用 t 对应的值,也就是 "try" ,即使在 finally 语句中把引用 t 指向了值 "finally" ,因为 return 的返回引用已经不是 t ,所以引用 t 的对应的值和 try 语句中的返回值无关了。

下面再看一个例子:

例2
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test()); // print finally
    }
}

这里稍微修改了 第一段代码,只是在 finally 语句块里面加入了 一个 return t 的表达式。

按照第一段代码的解释,先进行try{}语句,然后在 return 之前把当前的t的值 try 保存到一个变量 t',然后执行 finally 语句块,修改了变量 t 的值,在返回变量 t。

这里面有两个return语句,但是程序到底返回的是 "try" 还是 "finally" 。接下来我们还是看字节码信息

代码语言:txt
复制
  public static java.lang.String test();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=0
         0: ldc           #2                  // String
         2: astore_0
         3: ldc           #3                  // String try
         5: astore_0
         6: aload_0
         7: astore_1
         8: ldc           #4                  // String finally
        10: astore_0
        11: aload_0
        12: areturn
        13: astore_1
        14: ldc           #6                  // String catch
        16: astore_0
        17: aload_0
        18: astore_2
        19: ldc           #4                  // String finally
        21: astore_0
        22: aload_0
        23: areturn
        24: astore_3
        25: ldc           #4                  // String finally
        27: astore_0
        28: aload_0
        29: areturn
      Exception table:
         from    to  target type
             3     8    13   Class java/lang/Exception
             3     8    24   any
            13    19    24   any
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           14      10     1     e   Ljava/lang/Exception;
            3      27     0     t   Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 13
          locals = [ class java/lang/String ]
          stack = [ class java/lang/Exception ]
        frame_type = 74 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]

继续看code属性

前10行第一段代码逻辑一样,重点看11行

11行执行 finally 里面的赋值语句,把变量 t 赋值为 "finally",然后返回t对应的值

我们发现try语句中的 return 语句给忽略。可能 jvm 认为一个方法里面有两个 return 语句并没有太大的意义,所以 try 中的 return 语句给忽略了,直接起作用的是 finally 中的 return 语句,所以这次返回的是 "finally"。

再看看复杂一点的例子:

例3
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test()); // print catch 
    }
}

这里面 try 语句里面会抛出 java.lang.NumberFormatException,所以程序会先执行 catch 语句中的逻辑,t 赋值为 catch ,在执行return 之前,会把返回值保存到一个临时变量里面 t ',执行 finally 的逻辑,t 赋值为 "finally",但是会返回 t',所以变量 t 的值和返回值已经没有关系了,返回的是 "catch"

例4
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test()); // print finally
    }
}

这个和例 2 有点类似,由于 try 语句里面抛出异常,程序转入 catch 语句块,catch 语句在执行 return 语句之前执行 finally ,而 finally语句有 return ,则直接执行 finally 的语句值,返回 "finally".

例5
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            Integer.parseInt(null);
            return t;
        } finally {
            t = "finally";
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());// throw java.lang.NumberFormatException 
    }
}

这个例子在catch语句块添加了Integer.parser(null)语句,强制抛出了一个异常。然后 finally 语句块里面没有 return 语句。

继续分析一下,由于 try 语句抛出异常,程序进入 catch 语句块,catch 语句块又抛出一个异常,说明 catch 语句要退出,则执行 finally语句块,对 t 进行赋值。然后 catch 语句块里面抛出异常。

结果是抛出java.lang.NumberFormatException异常

例6
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            Integer.parseInt(null);
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test()); // print finally
    }
}

这个例子和上面例子中唯一不同的是,这个例子里面 finally 语句里面有 return 语句块。try catch 中运行的逻辑和上面例子一样,当catch 语句块里面抛出异常之后,进入 finally 语句快,然后返回 t 。则程序忽略 catch 语句块里面抛出的异常信息,直接返回 t 对应的值 也就是 "finally"。方法不会抛出异常

例7
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (NullPointerException e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test()); // throw java.lang.NumberFormatException
    }
}

这个例子里面 catch 语句里面 catch 的是 NPE 异常,而不是 java.lang.NumberFormatException异常,所以不会进入 catch 语句块,直接进入 finally 语句块,finally 对 t 赋值之后,由 try 语句抛出java.lang.NumberFormatException异常。

例8
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (NullPointerException e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test()); // print finally
    }
}

和上面的例子中 try catch 的逻辑相同,try 语句执行完成执行 finally 语句,finally 对 t 赋值 并且返回 ,最后程序结果返回 "finally"

例9
代码语言:txt
复制
public class TryCatchFinally {

    public static String test() {
        String t = "";

        try {
            t = "try";
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            String.valueOf(null);
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }
}

这个例子中,对 finally 语句中添加了String.valueOf(null), 强制抛出 NPE 异常。首先程序执行 try 语句,再执行finally语句块,finally 语句抛出 NPE 异常,整个结果返回NPE异常。

总结
  1. try、catch、finally 语句中,在如果 try 语句有 return 语句,则返回当前 try 中变量指向的值,此后 变量 指向的改变都不会影响 try 中 return 的返回
  2. 如果 finally 块中有 return 语句,则 try 或 catch 中的返回语句会被忽略
  3. 如果 finally 块中抛出异常,则整个 try、catch、finally块中抛出异常
  4. 【强制】不要在 finally 块中使用 return

The END.

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 例1
  • 例2
  • 例3
  • 例4
  • 例5
  • 例6
  • 例7
  • 例8
  • 例9
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档