Java 循环中对象复用导致属性覆盖?从 JVM 内存模型讲解原因

Java 循环中对象复用导致属性覆盖?从 JVM 内存模型讲解原因

    正在检查是否收录...

前言:前几天下午写代码的时候遇到一个bug,是一个比较基础的问题,关于对象引用,如果只是解决问题,那么就没有写这篇文章的必要,主要是站在jvm的角度上讲一讲这个问题

//国家,币种 一个国家可以对应多个币种 bankCountries是以及处理好的结果集 //CountryCurrency:String country; List<String> currencies //CountryCurrTmp:String country; String currency; String payNo; List<CountryCurrTmp> countryCurr = new ArrayList<>(); for (ClientCountry bankCountry : bankCountries) { CountryCurrTmp countryCurrTmp = new CountryCurrTmp(); // 业务1~ 国家转编码 NationalityInfoEnum enumByShortCode = NationalityInfoEnum.getEnumByShortCode(bankCountry.getCountry()); if(enumByShortCode!=null){ countryCurrTmp.setCountry(enumByShortCode.getCode()); } // 业务2~ 币种转编码 for (String currency : bankCountry.getCurrencies()) { CurrencyEnum currencyEnum = CurrencyEnum.getByCurrCode(currency); if (currencyEnum != null) { countryCurrTmp.setCurrency(currencyEnum.getNumCode()); } countryCurrTmp.setPayNo(payNo); countryCurr.add(countryCurrTmp); } } return countryCurr; 

结果

:写这段代码 我期待的结果是得到一个CountryCurrTmp集合 里面是处理好的 国家 币种 以及对应的支付方式,但实际上执行上述代码会发现 每一个country对应的币种最终都会一样且是内层循环最后遍历的币种.

分析

:站在jvm的角度上解释一下这个问题,外层for循环中countryCurrTmp对象创建,是在堆内存中开辟一片空间来存放的,而当线程执行到这段代码的时候,会在虚拟机栈中创建一个栈帧(包含局部变量表,操作数栈),而countryCurrTmp作为局部变量,存放在局部变量表中的是引用(堆中的地址)而不是副本,同一个国家 每次内循环赋值currency的时候都修改的是同一个堆内存中的对象,即造成了对象的复用,所以才会造成结果集每一个country对应的币种都是最后赋值的那个,从而使数据出现问题.下面会讲一个详细的实例

示例

:

  • 1.外层循环创建CountryCurrTmp时,JVM 会在堆内存中开辟一块空间(比如地址0x123),存储该对象的country“美国”、初始currency(空)等属性;
  • 2.线程执行这段代码时,会在虚拟机栈中创建一个 “方法栈帧”,其中的 “局部变量表” 会存储countryCurrTmp这个变量的引用,而是堆内存的地址0x123;
  • 3.进入内层循环遍历币种:第一次改currency为 “USD”,本质是通过0x123找到堆中的对象,修改其currency字段;第二次改currency为“EUR”,还是通过同一个0x123修改同一个堆对象 —— 最终list中添加的 2 个元素,都是指向0x123的引用,自然会显示同一个“EUR”。

总结

一下,当初写代码的时候我没有意识到这个问题,就说明我对这个知识点不熟练(JVM对象引用),有了一些业务的干扰就写出了错误的代码,所以写篇文章分享一下吧. 最后改后的代码:

List<CountryCurrTmp> tmpCountryCurr = new ArrayList<>(); for (ClientCountry bankCountry : bankCountries) { // 1. 转换国家代码 NationalityInfoEnum countryEnum = NationalityInfoEnum.getEnumByShortCode(bankCountry.getCountry()); if (countryEnum == null) { log.warn("未找到对应的国家枚举,跳过处理: 国家代码={}", bankCountry.getCountry()); continue; } // 2. 币种存储数字编码 for (String currency : bankCountry.getCurrencies()) { CurrencyEnum currencyEnum = CurrencyEnum.getByCurrCode(currency); if (currencyEnum == null) { log.warn("未找到币种的数字编码,跳过处理: 币种代码={}", currency); continue; } CountryCurrTmp countryCurrTmp = new CountryCurrTmp(); countryCurrTmp.setCountry(countryEnum.getCode()); countryCurrTmp.setPayNo(payNo); countryCurrTmp.setCurrency(currencyEnum.getNumCode()); tmpCountryCurr.add(countryCurrTmp); } } return tmpCountryCurr; 
  • 本文作者:WAP站长网
  • 本文链接: https://wapzz.net/post-27551.html
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
本站部分内容来源于网络转载,仅供学习交流使用。如涉及版权问题,请及时联系我们,我们将第一时间处理。
文章很赞!支持一下吧 还没有人为TA充电
为TA充电
还没有人为TA充电
0
0
  • 支付宝打赏
    支付宝扫一扫
  • 微信打赏
    微信扫一扫
感谢支持
文章很赞!支持一下吧
关于作者
2.8W+
9
1
2
WAP站长官方

使用 LLM + MCP 在过早客论坛冲浪&#127940;‍♀️

上一篇

记一次 .NET 某放射治疗光学定位软件 卡死分析

下一篇
评论区
内容为空

这一切,似未曾拥有

  • 复制图片
按住ctrl可打开默认菜单