出现这个问题的实际开发场景
我在开发知识点目录下知识点的新增挂载时,新增挂载的知识点与整棵树中挂载的所有的知识点不能重复,就是加一个过滤条件。 原来的代码片段
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了,后续用到它的地方那肯定都出错了。
秉承着遇事不慌的原则,我先再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);
}
}
经过我的查阅我终于完全明白了这个拷贝的原理,我就来给大家总结总结吧!
拷贝的种类
拷贝分为: 引用拷贝、对象拷贝
而对象拷贝分为:浅拷贝、深拷贝
引用拷贝
引用拷贝就是我实习写接口时碰到的情况,单纯的把一个变量赋给另一个变量,其实复制的是对象的地址,两个变量指向的还是同一个地址,改变其中任何一个变量的属性时,其他变量的该属性也会跟着变。例如:
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等)的方式实现。
这下就真正理解了这个拷贝的原理了孩子们!!! 项目不停迭代,我的活也变多了,后续我还会再分享一下实际开发场景碰到的问题!