实习过程中碰到的拷贝问题

327 阅读3分钟

出现这个问题的实际开发场景

我在开发知识点目录下知识点的新增挂载时,新增挂载的知识点与整棵树中挂载的所有的知识点不能重复,就是加一个过滤条件。 原来的代码片段

List<Long> pointIdList = knowledgePointIdsRequest.getPointIds();
if (Objects.nonNull(pointIds)) {
    knowledgePointIdsRequest.getPointIds().retainAll(pointIds);
    if (!knowledgePointIdsRequest.getPointIds().isEmpty()) {
        throw new BusinessException(BusinessCodes.KNOWLEDGE_POINT_EXIST);
    }
}

这里由于retainAll这个方法改变了knowledgePointIdsRequest.getPointIds(),所以我刚开始是想把它存到另一个变量里,后续再用这个变量(刚开始我还觉得自己挺细节的,这一点想到了,谁知打脸来的如此之快)后续一顿操作把接口写完了,开始本地swagger测了,一测发现报错了,那就开始打断点debug,最终发现pointIdList怎么莫名奇妙变成null了,后续用到它的地方那肯定都出错了。

微信图片_20241010173150.jpg

秉承着遇事不慌的原则,我先再debug了一遍,发现在执行完retainAll方法后,pointIdList的值也发生了改变,于是就问leader了,他一眼就看出来了,说我这里的赋值是引用拷贝,两个变量指向的还是同一个对象,其中一个变量修改了,另一个变量也会改变。眼前一亮,这个拷贝不就是我之前背的八股文嘛(但是不会用),现在还真让我碰到了,我就去网上查这些东西来弥补一下我这块知识点的缺失,现在终于弄清楚了。 这是改正后的代码片段:

List<Long> pointIdList = new ArrayList<>();
pointIdList.addAll(knowledgePointIdsRequest.getPointIds());
if (Objects.nonNull(pointIds)) {
    knowledgePointIdsRequest.getPointIds().retainAll(pointIds);
    if (!knowledgePointIdsRequest.getPointIds().isEmpty()) {
        throw new BusinessException(BusinessCodes.KNOWLEDGE_POINT_EXIST);
    }
}

经过我的查阅我终于完全明白了这个拷贝的原理,我就来给大家总结总结吧!

u=2657241801,3143797527&fm=253&fmt=auto&app=120&f=JPEG.webp

拷贝的种类

拷贝分为: 引用拷贝、对象拷贝

而对象拷贝分为:浅拷贝、深拷贝

引用拷贝

引用拷贝就是我实习写接口时碰到的情况,单纯的把一个变量赋给另一个变量,其实复制的是对象的地址,两个变量指向的还是同一个地址,改变其中任何一个变量的属性时,其他变量的该属性也会跟着变。例如:

Man man1=new man(18);
 Man man2=man1;
 man2.age=20;
 
 System.out.println(man1 == man2);//true
 System.out.println(man1.age);//20

浅拷贝与深拷贝

浅拷贝与深拷贝都是拷贝对象本身,拷贝后的两个变量指向的是不同的地址,但是他们的区别在于: 浅拷贝对于基本类型属性,浅拷贝会直接复制它们的值;而对于引用类型属性(数组,类,接口,字符串这些),浅拷贝会复制引用,而不是引用所指向的对象。 Object提供了一个clone()方法,它是protected的,我们的类如果不重写该方法将其声明成public,外部就调用不了了,我们可以通过重写clone方法来实现浅拷贝和深拷贝。 例如:

@Data
public class Man implements Cloneable{
    public int age;
    public String name;
    public int[] arr=new int[]{1,2,3};
    @Override
    public Man clone() throws CloneNotSupportedException {
        return (Man) super.clone();
    }
    @Override
    //重写ToString()...
}
Man man1=new man(18,"曼巴out");
 Man man2=man1.clone();
 man2.age=20;
 man2.name="不out";
 man2.arr={1,2};
 
 System.out.println(man1 == man2);//false
 System.out.println(man1.age);//18
 System.out.println(man1.name);//"曼巴out"(虽然String是引用类型,但是它是不可变的)
 System.out.println(man1.arr);//{1,2}    

除了手动实现的clone方法来实现浅拷贝,我们还可以通过构造函数、一些工具类来实现。

深拷贝则不管是什么类型的属性,都会直接复制他们的值。例如:

@Data
public class Man implements Cloneable{
    public int age;
    public String name;
    public int[] arr=new int[]{1,2,3};
    @Override
    public Man clone() throws CloneNotSupportedException {
        Man man =(Man) super.clone();
        man.name = this.name.clone();
        return man;
    }
    @Override
    //重写ToString()...
}
Man man1=new man(18,"曼巴out");
 Man man2=man1.clone();
 man2.age=20;
 man2.name="不out";
 man2.arr={1,2};
 
 System.out.println(man1 == man2);//false
 System.out.println(man1.age);//18
 System.out.println(man1.name);//"曼巴out"
 System.out.println(man1.arr);//{1,2,3}
 

除了手动实现的clone方法来实现深拷贝,我们还可以通过序列化、第三方库(SerializationUtils等)的方式实现。

这下就真正理解了这个拷贝的原理了孩子们!!! 项目不停迭代,我的活也变多了,后续我还会再分享一下实际开发场景碰到的问题!

u=1690196897,3387095815&fm=253&fmt=auto&app=138&f=JPEG.webp