震惊!Fastjson!JSONObject/JSONArray深浅拷贝踩坑,如何深度复制对象

一、写在前面

  昨天周五,在开发某个接口时遇到了一个非常坑的问题(当然是因为自己基础不够扎实),没有注意到JSONArray和JSONObject深拷贝与浅拷贝的问题,在坑了呆了一下午才出来,心态濒临崩溃……,如果你也遇到了我下面提到的问题,请往后看,我会奉上几种解决方法。

二、问题描述

  环境:我们在后台定义了一个公共ConcurrentHashMap<String,JSONArray>,作为数据的缓存Map,Map的Key为表名,Value为JSONArray格式的集合数据。——这是一切的前提。

  需求就是:对于Map中某张表的每条记录的某字段进行条件拼装,然后返回给前台。就这么简单的一个需求差点把我心态搞爆炸。我从Map中取出JSONArray数据后,遍历对字段进行修改,然后发现Map中的原始数据也跟着改变了,天哪,当时觉得可诡异了

三、问题模型抽取

  IDEA中准备测试环境我就不贴出来了,不是本文的重点,主要就是maven中导入fastjson的依赖坐标,乐意用Junit的导入依赖,不乐意用的就用psvm生成main方法测试就行。测试数据准备如下:

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.junit.Test;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @version V1.0
 * @Title:
 * @Package PACKAGE_NAME
 * @Description: TODO
 * @author: zongshaofeng
 * @date 2021/5/22
 */
public class TestFastjson {
    private final static Map<String, JSONArray> map = new ConcurrentHashMap<String, JSONArray>();

    static {
        //初始化map
        JSONArray jsonArray = new JSONArray();
        for (int i = 0; i < 3; i++) {
            JSONObject user = new JSONObject();
            user.put("name", "test" + i);
            user.put("age", 18 + i);
            user.put("birthday", new Date());
            user.put("content", "content"+i);
            jsonArray.add(user);
        }
        map.put("userTable", jsonArray);
    }

    @Test
    public void test(){
        System.out.println(map);
    }

}

打印Map结果如下:

{userTable=[{"birthday":1621678381131,"name":"test0","age":18,"content":"content0"},{"birthday":1621678381131,"name":"test1","age":19,"content":"content1"},{"birthday":1621678381131,"name":"test2","age":20,"content":"content2"}]}

四、正文,开始敲代码,首先模拟重现问题

	@Test
    public void recurringProblem() {
        //首先从Map取出JSONArray
        JSONArray userDataJSONArray = map.get("userTable");
        for (int i = 0; i < userDataJSONArray.size(); i++) {
            JSONObject userJSONObject = userDataJSONArray.getJSONObject(i);
            String content = userJSONObject.getString("content");
            content += "+++模拟拼接content字段内容";
            userJSONObject.put("content",content);
        }
        System.out.println("打印userDataJSONArray对象"+userDataJSONArray);
        System.out.println("打印Map中的userTable对象"+map.get("userTable"));
    }

结果:
在这里插入图片描述
  天哪!!!!我都已经将JSONArray取出来了,为啥我改了JSONArray元素的字段值,Map中的数据也跟着变了?

五、问题解决的探索过程

  大家是不是第一时间就想到,那我new一个新的JSONArray或者JSONObject是不是就可以了,毕竟我们在学习Java基础时,很确定new出来的两个看起来一模一样的对象equals不相等。我一开始下意识的也是这么想的,并付诸行动。如下所示:

@Test
    public void recurringProblem() {
        //首先从Map取出JSONArray
        JSONArray tempJSONArray = map.get("userTable");
        JSONArray userDataJSONArray=new JSONArray(tempJSONArray);
        for (int i = 0; i < userDataJSONArray.size(); i++) {
            JSONObject userJSONObject = userDataJSONArray.getJSONObject(i);
            String content = userJSONObject.getString("content");
            content += "+++模拟拼接content字段内容";
            userJSONObject.put("content",content);
        }
        System.out.println("打印userDataJSONArray对象"+userDataJSONArray);
        System.out.println("打印Map中的userTable对象"+map.get("userTable"));
    }

结果:
在这里插入图片描述
  从运行结果看,并没有解决问题。我再贴出两个对象的地址看看:
在这里插入图片描述
  两个对象的内存地址就是不一样的,但是问题依旧存在,那问题出在哪里呢?

  这个时候,源码伺候啊……让我们看一下JSONArray和JSONObject源码是怎么样的?
在这里插入图片描述
在这里插入图片描述
  是不是有种恍然大悟的感觉?属性里面都加了final字段,由final关键字的特性:“如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改”

  结论:new JSONArray(List list)和new JSONObject(Map<String, Object> map)并不能获得深度复制的对象,对象一改全改,正所谓一荣俱荣,一损俱损。

  写到这里,我还是能感觉到昨天下午面对这个问题我那痛苦的心情……

六、如何解决JSONArray/JSONObject浅拷贝问题(如何获得深拷贝对象)

  这个问题的解决其实有好几种方法,下面我给出两种,抛砖引玉,大家可以继续深入学习。

方法一:借助addAll()或者putAll()方法进行深度拷贝

我们对测试方法进行改造,使用addAll方法复制JSONArray,然后运行看结果

@Test
    public void recurringProblem() {
        //首先从Map取出JSONArray
        JSONArray jsonArray = map.get("userTable");
        JSONArray userDataJSONArray = new JSONArray();
        userDataJSONArray.addAll(jsonArray);
        for (int i = 0; i < userDataJSONArray.size(); i++) {
            JSONObject userJSONObject = userDataJSONArray.getJSONObject(i);
            String content = userJSONObject.getString("content");
            content += "+++模拟拼接content字段内容";
            userJSONObject.put("content", content);
        }
        System.out.println("打印userDataJSONArray对象" + userDataJSONArray);
        System.out.println("打印Map中的userTable对象" + map.get("userTable"));
    }

在这里插入图片描述
  发现了吗?为啥结果还是有问题呢?这就又涉及到上述的原理了,JSONArray现在是深度拷贝出来了,对复制出来的JSONArray对象进行添加或者删除JSONObject对象完全不会影响源对象的数据。

我贴一个测试截图证明我上述说法吧,不贴代码啦
在这里插入图片描述

  但是内部的JSONObject还是final修饰的呀,改动还是会影响的,因为堆里就那一份数据,因此这还没结束,还需要使用putAll()方法对JSONObject对象进行深度拷贝

继续改造:

@Test
    public void recurringProblem() {
        //首先从Map取出JSONArray
        JSONArray jsonArray = map.get("userTable");
        JSONArray userDataJSONArray = new JSONArray();
        userDataJSONArray.addAll(jsonArray);
        for (int i = 0; i < userDataJSONArray.size(); i++) {
            JSONObject jsonObject = userDataJSONArray.getJSONObject(i);
            JSONObject userJSONObject=new JSONObject();
            userJSONObject.putAll(jsonObject);
            String content = userJSONObject.getString("content");
            content += "+++模拟拼接content字段内容";
            userJSONObject.put("content", content);
            userDataJSONArray.set(i,userJSONObject);
        }
        System.out.println("打印userDataJSONArray对象" + userDataJSONArray);
        System.out.println("打印Map中的userTable对象" + map.get("userTable"));
    }

在这里插入图片描述
  测试结果显示,深度拷贝成功。

方法二:先将源对象转成JSON字符串,再解析成新对象

@Test
    public void recurringProblem() {
        //首先从Map取出JSONArray
        JSONArray jsonArray = map.get("userTable");
        JSONArray userDataJSONArray= JSON.parseArray(jsonArray.toJSONString());
        for (int i = 0; i < userDataJSONArray.size(); i++) {
            JSONObject userJSONObject = userDataJSONArray.getJSONObject(i);
            String content = userJSONObject.getString("content");
            content += "+++模拟拼接content字段内容";
            userJSONObject.put("content",content);
        }
        System.out.println("打印userDataJSONArray对象"+userDataJSONArray);
        System.out.println("打印Map中的userTable对象"+map.get("userTable"));
    }

在这里插入图片描述
  测试结果显示,深度拷贝成功。

七、写在最后

  学习永无止境,且行且珍惜。小宗于2021年5月22日19:39:53,济南。

  共和国于今日痛失两位院士,愿世间如您所愿,医食无忧,一路走好!

  我辈当自强!生命不息,学习不止!

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小宗啊?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值