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.mzph.cn/news/211770.shtml

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

相关文章

报错:Parsed mapper file: ‘file mapper.xml 导致无法启动

报错 : Logging initialized using class org.apache.ibatis.logging.stdout.StdOutImpl adapter. Registered plugin: com.github.yulichang.interceptor.MPJInterceptor3b2c8bda Parsed mapper file: file [/Mapper.xml] application无法启动 我这边产生原因是项…

! [remote rejected] master -> master (pre-receive hook declined)

! [remote rejected] master -> master (pre-receive hook declined) 如图: 出错解决方法 首先输入命令 git branch xindefenzhi然后,进入这个新创建的分支 git checkout branch然后重新git push就可以了

爬虫学习-基础库的使用(urllib库)

目录 一、urllib库介绍 二、request模块使用 (1)urlopen ①data参数 ②timeout参数 (2)request (3)高级用法 ①验证 ②代理 ③Cookie 三、处理异常 ①URLError ②HTTPError 四、解析链接 ①urlparse ②…

Kernel(一):基础

本文主要讨论210的kernel基础相关知识。 内核驱动 驱动是内核中的硬件设备管理模块,工作在内核态,程序故障可能导致内核崩溃,程序漏洞会使内核不安全 根文件系统提供根目录,进程存放在根文件系统中,内核启动最后会装载根文件系统 应用程序不属于内核,…

1828_ChibiOS中的对象FIFO

全部学习汇总: GreyZhang/g_ChibiOS: I found a new RTOS called ChibiOS and it seems interesting! (github.com) 1. 最初的这个理解,当看到后面之后就知道有点偏差了。其实,这个传输就是一个单纯的FIFO而不是两个FIFO之间的什么操作。 2.…

去掉参数中第一个“,”

记录一下,前端传参中,传给我参数是“categoryIds: ,1731557494586241026,1731569816263311362,1731569855534579713,1731858335179223042,1731858366821052418” 但是后端,因为我的mybati是in查询,所以因为第一个是“,”。所以会导…

sap增强

四代增强 2种显示增强1种隐式增强 隐式增强 光标放在增强点或其中的代码点击修改即可修改代码 显示增强 1.ENHANCEMENT-POINT 在代码修改界面选择空行 光标所在位置 可以创建多个增强实施且激活后都会执行. 2.ENHANCEMENT-SECTION 1,选中程序中空行 2.编辑->创建选项 …

回顾2023 亚马逊云科技 re_Invent,创新AI,一路同行

作为全球云计算龙头企业的亚马逊云科技于2023年11月27日至12月1日在美国拉斯维加斯举办了2023 亚马逊云科技 re:Invent,从2012年开始举办的亚马逊云科技 re:Invent 全球大会,到现如今2023 亚马逊云科技 re:Invent,回顾历届re:Invent大会,亚马…

C++『异常』

✨个人主页: 北 海 🎉所属专栏: C修行之路 🎃操作环境: Visual Studio 2022 版本 17.6.5 文章目录 🌇前言🏙️正文1.异常基本概念1.1.C语言异常处理方式1.2.C异常处理方式 2.异常的使用2.1.异常…

在线网页生成工具GrapesJS

项目地址 https://github.com/GrapesJS/grapesjshttps://github.com/GrapesJS/grapesjs 项目简述 这是一个基于node.js的在线网页生成项目,对简化开发有很大的帮助。 主要使用的语言如下: 编辑页面如下: 使用也很简洁 具体可以看下项目。…

12. MySQL 锁机制

目录 概述 MylSAM引擎 InnoDB引擎 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资如何保证数据…

2023年第十届GIAC全球互联网架构大会-核心PPT资料下载

一、峰会简介 谈到一个应用,我们首先考虑的是运行这个应用所需要的系统资源。其次,是关于应用自身的架构模式。最后,还需要从软件工程的不同角度来考虑应用的设计、开发、部署、运维等。架构设计对应用有着深远的影响,它的好坏决…

Leetcode659. 分割数组为连续子序列

Every day a Leetcode 题目来源:659. 分割数组为连续子序列 解法1:哈希 贪心 定义两个哈希表: numsCount:统计数组 nums 中各元素出现次数。tailCount:存储以数字 i 结尾的且符合题意的连续子序列个数。 算法&a…

极兔单号查询,极兔快递物流查询,一键筛选出退回件

批量查询极兔快递单号的物流信息,一键筛选出其中的退回件。 所需工具: 一个【快递批量查询高手】软件 极兔快递单号若干 操作步骤: 步骤1:运行【快递批量查询高手】软件,并登录 步骤2:点击主界面左上角的…

【Bootloader学习理解----跳转优化异常】

笔者接着来介绍一下Bootloader的跳转代码以及优化 1、跳转代码理解 跳转代码可能要涉及到芯片架构的知识,要跳转到对应的位置,还要设置相关的SP 堆栈指针,具体可以参考笔者这篇文章BootLoader的理解与实现。 STM32的跳转代码如下所示: u32 …

基于以太坊的智能合约开发Solidity(基础篇)

参考教程:基于以太坊的智能合约开发教程【Solidity】_哔哩哔哩_bilibili 1、第一个程序——Helloworld: //声明版本号(程序中的版本号要和编译器版本号一致) pragma solidity ^0.5.17; //合约 contract HelloWorld {//合约属性变…

Python轴承故障诊断 (四)基于EMD-CNN的故障分类

目录 前言 1 经验模态分解EMD的Python示例 2 轴承故障数据的预处理 2.1 导入数据 2.2 制作数据集和对应标签 2.3 故障数据的EMD分解可视化 2.4 故障数据的EMD分解预处理 3 基于EMD-CNN的轴承故障诊断分类 3.1 训练数据、测试数据分组,数据分batch 3.2 定义…

stu05-前端的几种常用开发工具

前端的开发工具有很多,可以说有几十种,包括记事本都可以作为前端的开发工具。下面推荐的是常用的几种前端开发工具。 1.DCloud HBuilder(轻量级) HBuilder是DCloud(数字天堂)推出的一款支持HTML5的web开发…

硬件开发笔记(十四):RK3568底板电路LVDS模块、MIPI模块电路分析、LVDS硬件接口、MIPI硬件接口详解

若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134634186 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

软考高级备考-系统架构师(机考后新版教材的备考过程与资料分享)

软考高级-系统架构设计师 考试复盘1.考试结果2.备考计划3.个人心得 资料分享 考试复盘 1.考试结果 三科压线过,真是太太太太太太太幸运了。上天对我如此眷顾,那不得不分享下我的备考过程以及一些备考资料,帮助更多小伙伴通过考试。 2.备考…