前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 异常处理中篇:finally 中的陷阱(finally 中 return 会发生什么)

Java 异常处理中篇:finally 中的陷阱(finally 中 return 会发生什么)

原创
作者头像
Lorin 洛林
发布2023-12-15 20:35:33
2644
发布2023-12-15 20:35:33
举报
文章被收录于专栏:Java 技术小屋Java 技术小屋

前言

  • 在上一篇文章中,我们介绍了 Java 异常的基本概念,Throwable 、异常处理关键字:try-catch-finally、throw、throws;本篇文章我们将更加深入的了解 finally 在异常处理中的常见问题和底层原理。

版本

  • Java 8

finally 中的陷阱

  • 我们知道无论是否发生异常还是 try 或 catch 中存在 returnfinally 都会执行,下面我们来看看下面几种场景:

finally 中使用 return

  • 当我们在 finally 中使用 return 时,trycatch 中的 return 会失效或异常丢失(见下文),会在 finally 直接返回。
代码语言:java
复制
public class Main {
    public static void main(String[] args) {
        System.out.println(extracted());
    }

    private static int extracted() {
        int a = 1;
        try {
            a = 2;
            a = a / 0;
            return a;
        } catch (Exception e) {
            System.out.println(e);
            return a;
        } finally {
            System.out.println("this is finally");
            return -1;
        }
    }
}

// 输出 finally 中直接 return -1 
java.lang.ArithmeticException: / by zero
this is finally
-1 

finally 中修改数据的影响

  • 如果你在 finally 代码块中修改了数据,你可能会有一些奇妙的体验。

基本类型

代码语言:java
复制
public class Main {
    public static void main(String[] args) {
        System.out.println(extracted());
    }

    private static int extracted() {
        int a = 1;
        try {
            a = 2;
            return a;
        } finally {
            System.out.println("this is finally");
            a += 3;
        }
    }
}

// 输出
this is finally
2
  • 我们可以得出结论在 finally 中修改基本类型不会影响 try 、catchreturn 中的返回值(但是会影响 finally 中的 return ,见下面的案例)。
代码语言:java
复制
public class Main {
    public static void main(String[] args) {
        System.out.println(extracted());
    }

    private static int extracted() {
        int a = 1;
        try {
            a = 2;
            return a;
        } finally {
            System.out.println("this is finally");
            a += 3;
            return a;
        }
    }
}

// 输出
this is finally
5

引用类型

代码语言:java
复制
// 案例一
public class Main {
    public static void main(String[] args) {
        System.out.println(extracted());
    }

    private static Object extracted() {
        Person person = new Person();
        try {
            return person;
        } finally {
            System.out.println("this is finally");
            person.age = 5;
        }
    }
}

class Person {
    int age;

    @Override
    public String toString() {
        return "Person age= " + age;
    }
}

// try 中的 return 被修改
this is finally
Person age= 5

// 案例二
public class Main {
    public static void main(String[] args) {
        System.out.println(extracted());
    }

    private static Object extracted() {
        Person person = new Person();
        try {
            return person;
        } finally {
            System.out.println("this is finally");
            person = (new Person());
            person.age = 3;
        }
    }
}

class Person {
    int age;

    @Override
    public String toString() {
        return "Person age= " + age;
    }
}

// try 中的 return 没有被修改
this is finally
Person age= 0
  • 上面的结果看着有点奇怪但实际上很好理解,我们在以前的文章中讲过,Java 实际上只有值传递而不存在引用传递,当为返回值为引用类型时,返回的其实是一个地址,在案例一中我们使用地址修改了原内容,而在案例二中,我们其实将 person 指向了新的地址(new Person()),因此并没有修改原返回值地址的内容。

finally 中的代码 “非最后” 执行

  • 有时候我们发现 finally 中的代码 “非最后” 执行,那么有可能是并行执行了,比如:
代码语言:java
复制
public class Main {
    public static void main(String[] args) {
       extracted();
    }

    private static void extracted() {
        try {
            throw new IllegalStateException();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("this is finally");
        }
    }
}

// 比较难出现
this is finally
java.lang.IllegalStateException
	at Main.extracted(Main.java:9)
	at Main.main(Main.java:4)
  • 实际上是因为 e.printStackTrace() 使用的是 System.err,而 System.out.println 使用的是 System.out,标准输出流和标准错误输出流是彼此独立执行的,且 JVM 为了高效的执行会让二者并行运行,所以会出现finally 中的代码 “非最后” 执行的场景。

finally 代码块一定会执行?

  • 虽然这里有一定抬杠的嫌疑,但实际上确实有一些场景下 finally 代码块不会执行,比如:
代码语言:text
复制
在 try-catch 语句中执行了 System.exit
在 try-catch 语句中出现了死循环
在 finally 执行之前 JVM 崩溃
  • try-catch 语句中执行了 System.exit
代码语言:java
复制
public class Main {
    public static void main(String[] args) {
       extracted();
    }

    private static void extracted() {
        try {
            // 此代码块执行完程序退出
            System.exit(0);
            throw new IllegalStateException();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("this is finally");
        }
    }
}

异常丢失

  • 如果我们在 finally 代码块中抛出异常或使用 retrun,将会导致我们 try-catch 中的异常丢失。
代码语言:java
复制
// 案例一
public class Main {
    public static void main(String[] args) throws Exception {
        extracted();
    }

    private static void extracted() throws Exception {
        try {
            throw new IllegalStateException();
        } finally {
            throw new Exception("Exception");
        }
    }
}

// 输出
Exception in thread "main" java.lang.Exception: Exception
	at Main.extracted(Main.java:11)
	at Main.main(Main.java:4)

// 案例二
public class Main {
    public static void main(String[] args) throws Exception {
        extracted();
    }

    private static int extracted() throws Exception {
        try {
            throw new IllegalStateException();
        } finally {
            return 1;
        }
    }
}

finally 底层原理分析

  • 《The JavaTM Virtual Machine Specification, Second Edition》 一书中我们可以知道 Java 虚拟机是如何编译 finally:实际上,Java 虚拟机会把 finally 语句块作为 subroutine 直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值(基本类型值或地址)到本地变量表(Local Variable Table)中,待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。
  • 理解了 JVMfinally 的实现,我们其实就很好理解 finally 中修改数据的影响 中的案例,有兴趣的朋友可以下去深入了解。

总结

  • 本文我们结合了 finally 在实际使用中可能出现的问题并进行分析对应的原因,最后介绍了 finallyJVM 中的实现原理,帮助我们在日常开发的更好的使用 finally,下篇文章将会介绍实际异常处理中的一些最佳实践。

个人简介

? 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

? 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

? 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

? 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

? 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

? 保持关注我的博客,让我们共同追求技术卓越。

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 版本
  • finally 中的陷阱
    • finally 中使用 return
      • finally 中修改数据的影响
        • 基本类型
        • 引用类型
      • finally 中的代码 “非最后” 执行
        • finally 代码块一定会执行?
          • 异常丢失
            • finally 底层原理分析
            • 总结
            • 个人简介
            相关产品与服务
            云数据库 MySQL
            腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com