一、前言
前段时间小永哥分享了有关redis的一些常用技巧,小永哥打算以java实现lru算法作为redis篇的结尾,不是说redis只有这点东西,有两点原因,第一是小永哥资源有限,像哨兵模式、集群、哨兵模式也没有从0到1的搭建过程,只谈理论没有操作我也不会写,缺乏实践容易出坑,误导读友们,第二就是我水平有限,再写下去我容易把我不住,等水平再提升提升之后看再补上后续这些高阶的东西。
最近几天暂时先发几篇开发过程的小技巧和注意事项,顺便思考一下下一步写作的方向,这次先分享一篇关于对象拷贝的事情。
二、场景复现
先来说一个业务场景,我当时开发过一个功能,两颗树形数据比较差异,如果对象A在A树上存在而在B树上不存在,那么就将A对象也在B树中设置一份并且增加一个空行状态标识,到时候前端就会根据这个标识来渲染哪些数据是自身缺失的,为了方便测试,我们这次只弄两个集合进行模拟,来实现这个场景,用于引出我们后续的概念。
package com.relation;
import com.alibaba.fastjson2.JSON;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author huhy
* @version 1.0
* @Description:
* @ClassName Date:2025/4/20 21:11
*/
public class CloneTest {
@Test
public void testCompare() throws Exception{
Map<String,Object> attr = new HashMap<>();
attr.put("status","正常");
List<TestDemo> oneTestDemoList = new ArrayList<>();
oneTestDemoList.add(new TestDemo("测试T01","T01-CODE",attr));
oneTestDemoList.add(new TestDemo("无法无天","WFWT-CODE",attr));
oneTestDemoList.add(new TestDemo("小明","XIAOMING",attr));
List<TestDemo> twoTestDemoList = new ArrayList<>();
twoTestDemoList.add(new TestDemo("测试T01","T01-CODE",attr));
twoTestDemoList.add(new TestDemo("巨阙","JUQUE",attr));
twoTestDemoList.add(new TestDemo("小明","XIAOMING",attr));
//比较差异
listCompare(oneTestDemoList,twoTestDemoList);
listCompare(twoTestDemoList,oneTestDemoList);
//分别打印两个集合
System.out.println("oneTestDemoList: "+JSON.toJSONString(oneTestDemoList));
System.out.println("twoTestDemoList: "+JSON.toJSONString(twoTestDemoList));
}
/**
* 功能描述:
* @param:比较两个集合的差异,将缺失的对象补齐并修改状态为空行
* @Author:huhy
* @Date: 2025/4/21 22:21
*/
private void listCompare(List<TestDemo> oneTestDemoList,
List<TestDemo> twoTestDemoList)throws Exception{
for (TestDemo testDemo : oneTestDemoList) {
if(!check(testDemo,twoTestDemoList)){
testDemo.getAttr().put("status","空行");
twoTestDemoList.add(testDemo);
}
}
}
private boolean check(TestDemo testDemo,List<TestDemo> testDemoList){
String code = testDemo.getCode();
for (TestDemo demo : testDemoList) {
if(demo.getCode().equals(code)){
return true;
}
}
return false;
}
}


我们可以看到,【无法无天】这个对象,在集合2中缺失,所以将【无法无天】补全到集合2中设置为空行没问题,但是在集合1和集合2中状态全都变成了空行,这就不对,这个得排查一下代码。
有问题的代码也好找,就是下图中被框住的代码,我们找到差异之后,不能直接修改差异对象,应该将差异对象复制一份再进行操作。

我们对问题代码进行修改再运行看看效果。
private void listCompare(List<TestDemo> oneTestDemoList,
List<TestDemo> twoTestDemoList)throws Exception{
for (TestDemo testDemo : oneTestDemoList) {
if(!check(testDemo,twoTestDemoList)){
TestDemo copyTestDemo = testDemo.clone();
copyTestDemo.getAttr().put("status","空行");
twoTestDemoList.add(copyTestDemo);
}
}
}


好像没什么效果,不应该呀,我们已经拷贝了对象,为什么还是没效果,这个问题其实很简单,就是浅拷贝和深拷贝的问题,我们来看一下这两者的区别:
浅拷贝:浅拷贝创建一个新对象,但是会将原对象中的非基本类型字段的引用复制到新对象中。如果原对象中的非基本类型字段被更改,这些更改也会反映在新对象中。
深拷贝:对于深拷贝,你需要确保所有非基本类型的字段都被复制了一份新的实例。这通常涉及到递归克隆或者使用序列化与反序列化技术。
问题的根源就是浅拷贝造成的,所以我们使用深拷贝试一下效果,至于序列化和反序列化的技术,我本次使用JSON方式进行处理,如果大家对流感兴趣,可以试试用流进行序列化和反序列化。
private void listCompare(List<TestDemo> oneTestDemoList,
List<TestDemo> twoTestDemoList)throws Exception{
for (TestDemo testDemo : oneTestDemoList) {
if(!check(testDemo,twoTestDemoList)){
TestDemo copyTestDemo = JSON.parseObject(JSON.toJSONString(testDemo),TestDemo.class);
copyTestDemo.getAttr().put("status","空行");
twoTestDemoList.add(copyTestDemo);
}
}
}


如图所示,空行状态设置已经正常了,小永哥尝试过,其实如果空行状态不是属性组而是一个字符串或者数字类型,那么浅拷贝也是可以的,但属性组是一个对象,浅拷贝无法彻底复制一份全新的副本,必须得用深度拷贝来实现。
三、结语
这次分享的内容不是什么高深的东西,但万一遇到的话一定很恶心,因为演示代码我简化了逻辑复杂度所以可以一眼就能定位到,小永哥当时是两颗纵深不确定的树进行比较,在递归中进行差异比较,除了比较空行差异,如果不是空行差异,还要比较属性差异,在庞大的代码中快速定位问题还是很麻烦的,所以希望大家能够在平时的时候就养成好习惯,争取在写代码的时候就能提高准确度,减少bug,好了,本次就到这里了,晚安。

288

被折叠的 条评论
为什么被折叠?



