Java 匿名内部类使用的外部变量,为什么一定要加 final?

问题描述

在这里插入图片描述
在这里插入图片描述

Effectively final

  • Java 1.8 新特性,对于一个局部变量或方法参数,如果他的值在初始化后就从未更改,那么该变量就是 effectively final(事实 final)。 这种情况下,可以不用加 final 关键字修饰。

在这里插入图片描述
在这里插入图片描述

内部类会持有外部类对象的引用

  • 非静态内部类,持有外部类的引用;
    • 以编译器自动生成的成员变量的形式持有
    • 通过编译器自动生成的构造方法传入
  • 静态内部类,不持有外部类的引用

内部类

非静态内部类会通过自动生成的构造函数持有一个外部类对象的引用:

在这里插入图片描述
在这里插入图片描述

即便给内部类增加一个非默认的构造函数,编译器依然会自动为构造函数添加一个外部类对象的参数:

在这里插入图片描述

静态内部类

静态内部类自动生成的构造方法不会持有外部类的引用:

在这里插入图片描述

匿名内部类

匿名内部类也会通过构造函数持有一个外部类对象的引用:

在这里插入图片描述
在这里插入图片描述

匿名内部类会将捕获的局部变量在其构造函数中传入:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

匿名内部类捕获外部变量添加 final 的作用是:保证匿名内部类捕获的副本引用和外部的局部变量始终都指向同一个对象,也就是没有人可以修改它们的指向。

在这里插入图片描述

假如外部方法的局部变量不加final,有可能外部的局部变量的指向改变了,但是内部类却不知道。这样就可能导致内部类操作的对象和外部的局部变量指向的不是同一个对象,会出现 bug。

注意:匿名内部类捕获的局部变量加 final 是指的处于同一方法内的局部变量如果捕获的是所处的外部类中的变量,则不需要加 final,这是因为在这种情况下,可以直接通过外部类对象的引用来获取到其成员变量。

在这里插入图片描述
在这里插入图片描述

总结

  • 匿名内部类,持有外部类的引用;
    • 以编译器自动生成的成员变量的形式持有
    • 通过编译器自动生成的构造方法传入
    • 匿名内部类,通过这个引用访问外部类的成员变量和方法
  • 匿名内部类,访问外部局部变量时,其实是访问自身的一个成员变量;
    • 这个成员变量,是编译器自动生成的;
    • 这个成员变量,由编译器自动生成的构造方法初始化;
    • 为了保证这个成员变量和外部局部变量时刻保持一致性,二者必须都是final的。

根本原因就是为了保证内部和外部对这个局部变量的对象的操作保持一致性因此要求两个副本均不可变。

Kotlin 的匿名内部类

kotlin 中的匿名内部类不会外部变量时,即便不使用类似final的关键字修饰也能保证内部和外部访问的同一局部对象的一致性。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通过以上字节码分析可以得出结论:

  • Kotlin 的匿名内部类不会在构造函数中传入整个外部类对象的引用
  • 但是对于捕获的局部变量,会自动生成一个不可变(val)的保证类对象(ObjectRef)传入构造函数中。

对应的伪代码结构2如下:

在这里插入图片描述

ObjectRef 是什么:

在这里插入图片描述

我们看到它就是一个泛型类,内部持有一个 element泛型成员变量,可以认为是 Kotlin 为了解决捕获局部变量问题生成的一个装箱类

除了 ObjectRef,Kotlin 中还有 ByteRefShortRefIntRefLongRefFloatRefDoubleRefCharRefBooleanRef

所以虽然与 Java 的解决方式不同,但本质上看思想是一致的,都要保持内部和外部对捕获变量的操作一致性,即保证这两个副本的不可变性

在这里插入图片描述

虽然包装类对象的指向不可变,但是包装类对象里面包的东西是可以改变指向的,这一点比 Java 要优秀:

在这里插入图片描述

这一切都是编译器为我们自动实现的,但对于开发者而言,体验上就会跟 Java 有明显的不同:“Java 需要 final 捕获,但 Kotlin 不需要 val 捕获”,但这是一个错觉,实际上 Kotlin 也需要,只不过你看不到而已。

内存泄漏问题

内存泄漏的根本原因就是一个长生命周期的对象被一个短生命周期的对象所引用。

在这里插入图片描述

  • 一旦内部类对象被长生命周期对象引用,或自身生命周期过长,就会导致外部类无法被 GC 回收,因为它们在同一条引用链上,根据 GC Root 可达性分析算法判断为可达。
  • 比如在 Activity 中通过匿名内部类的方式创建的 HandlerAsyncTaskResultReceiver等。

解决方式:

  1. 不使用(匿名)内部类,将类的定义放在外部,显示的构造传参,并在外部类销毁时(如Activity.onDestroy())主动断开对外部类的引用(例如很多框架会提供解绑、反注册的API以便用户在页面销毁时进行调用)。
  2. 必须要使用内部类的场景,采用静态内部类 + 弱引用指向外部类对象的方式。(由于弱引用一旦被GC扫描发现就会回收,所以不存在内存泄漏问题)
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/263233.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

求导公式,求导的四则运算,复合函数求导

求导公式 求导的四则运算 复合函数求导

【Docker】Docker Compose,yml 配置指令参考的详细讲解

作者简介: 辭七七,目前大二,正在学习C/C,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: 七七的闲谈 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖&#x1f…

2c 操作符详解

文章目录 1. 操作符分类:2. 算术操作符3. 移位操作符(对二进制移位)3.1 左移操作符3.2 右移操作符 4. 位操作符(重要)5. 赋值操作符6. 单目操作符6.1 单目操作符介绍6.2 sizeof 和 数组 7. 关系操作符8. 逻辑操作符(重要)9. 条件操作符10. 逗号表达式11. 下标引用、…

订单系统的设计与海量数据处理实战

概述 订单系统可以说是整个电商系统中最重要的一个子系统,因此订单数据可以算作电商企业最重要的数据资产。订单系统从代码上来说可分为两部分:订单程序和历史订单处理程序。数据存储进行分库分表。 订单系统业务分析 对于一个合格的订单系统&#xf…

基础漏洞流量分析

基础漏洞流量分析 sql注入 sql注入原理 SQL 注入的攻击行为可以描述为通过用户可控参数中注入 SQL 语法,破坏原有 SQL 结构,达到编写程序时意料之外结果的攻击行为。其成因可以归结为以下两个原因叠加造成的: 程序员在处理程序和数据库交互时&#x…

CleanMyMac2024破解版激活码许可证密钥

CleanMyMac X是一款颇受欢迎的专业清理软件,拥有十多项强大的功能,可以进行系统清理、清空废纸篓、清除大旧型文件、程序卸载、除恶意软件、系统维护等等,并且这款清理软件操作简易,非常好上手,特别适用于那些刚入手苹…

云计算大屏,可视化云计算分析平台(云实时数据大屏PSD源文件)

大屏组件可以让UI设计师的工作更加便捷,使其更高效快速的完成设计任务。现分享可视化云分析系统、可视化云计算分析平台、云实时数据大屏的大屏Photoshop源文件,开箱即用! 若需 更多行业 相关的大屏,请移步小7的另一篇文章&#…

OpenSSL 编程指南

目录 前言初始化SSL库创建SSL 上下文接口(SSL_CTX)安装证书和私钥加载证书(客户端/服务端证书)加载私钥/公钥加载CA证书设置对端证书验证例1 SSL服务端安装证书例2 客户端安装证书创建和安装SSL结构建立TCP/IP连接客户端创建socket服务端创建连接创建SSL结构中的BIOSSL握手服务…

项目管理工具在电商新品运营中的应用与实践

运营永远是电商的核心命题,新品开发完成后,就进入了新品的运营工作,包括上架前的准备、上架后的推广、商品定位等,且运营一般同时负责多个新品的运营,任务多、时间紧、信息分散,如果缺乏统一管理入口&#…

如何防止恶意调用和攻击对抖音商品详情API的影响?

防止恶意调用和攻击对抖音商品详情API的影响是开发者和平台必须关注的问题。恶意调用和攻击可能导致服务中断、数据泄露或其他安全问题,对平台和用户造成损失。本文将介绍一些常见的恶意调用和攻击方式,并提出相应的防范措施,以确保抖音商品详…

Mysql8.0实现主从复制

1、什么是主从复制 数据库的主从复制(master-slave replication)是一种数据复制技术,其中一台数据库服务器(主服务器)上的数据变更会复制到另一台或多台数据库服务器(从服务器)上。这种复制可以…

代码序随想录二刷 |二叉树 | 二叉树的层序遍历II

代码序随想录二刷 |二叉树 | 二叉树的层序遍历II 题目描述解题思路代码实现 题目描述 107.二叉树的层序遍历II 给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层&#xff0…