鄙人写文章喜欢简洁点,希望用尽可能短的语句描述一个知识点
1、概述
拷贝的一个经典的使用场景:当前对象要传给其他多个方法使用,如果该对象在某一个方法中被修改,那么这个修改会影响到其他方法。 如果要避免这种影响,就需要给每一个方法都传入一个当前对象的拷贝。
深与浅拷贝的区别就在于对复杂对象的处理:对于基本类型,浅拷贝、深拷贝都是拷贝的值;对于引用类型浅拷贝的是对象的引用。而深拷贝则是直接新建一个对象实例。
注意浅拷贝中的复杂引用以及简单引用:对于简单引用,拷贝后的对象指向新的地址不会影响到原对象。复杂引用,拷贝后的对象将内部的对象指向新的地址,会影响到原对象对应的内部对象。这里一定理解下,抓住“地址”指向去理解就好了。
2、代码实现
(1)浅拷贝实现方式:类实现Cloneable接口,并且重写clone()方法。详细见下面的代码
(2)深拷贝:主要是针对类内部的复杂引用变量。两种方式:1)复杂引用也实现Cloneable,并重写clone()方法,然后在父对象重写的clone方法中将该复杂引用指向克隆后的引用 2)直接将需要克隆的对象进行序列化,然后反序列化就可以得到一个深拷贝的对象。
当然这些工作都有现成的轮子了,借助于Apache Commons工具类可以直接实现:
- 浅克隆:BeanUtils.cloneBean(Object obj);
- 深克隆:SerializationUtils.clone(T object);
当然可以直接使用
2.1浅拷贝
父子类
1 @Data 2 @Builder 3 class Father implements Cloneable { 4 Long age; 5 StringBuilder name; 6 Son son; 7 8 @Override 9 public Object clone() { 10 //浅拷贝 11 try { 12 return super.clone(); 13 } catch (CloneNotSupportedException e) { 14 e.printStackTrace(); 15 return null; 16 } 17 } 18 } 19 20 @Data 21 @Builder 22 class Son { 23 Long age; 24 String name; 25 int grade; 26 }
初始赋值
1 public static Son son = Son.builder().age(new Long(30L)).name("大儿子").grade(100).build(); 2 public static Father father = Father.builder().age(new Long(50L)).name("爸爸").son(son).build();
浅拷贝示例:
private static void shallowClone() { System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); //开始克隆 Father fatherInLaw = (Father) father.clone(); fatherInLaw.getSon().setAge(10L); fatherInLaw.getSon().setGrade(0); fatherInLaw.getSon().setName("继子"); fatherInLaw.setAge(new Long(80L)); fatherInLaw.setName("继父"); //修改后结果 System.out.println("==========浅拷贝后==========="); System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); }
结果输出
父亲年龄:50 父亲姓名:爸爸 儿子年龄:30 儿子姓名:大儿子 儿子分数:100 ==========浅拷贝后=========== 父亲年龄:50 父亲姓名:爸爸 儿子年龄:10 儿子姓名:继子 儿子分数:0
2.2.深拷贝
(1)子引用重写clone方法,并且在父类中改变子引用的指向
父子类
@Data @Builder class Father implements Cloneable { Long age; StringBuilder name; Son son; @Override public Object clone() { Father father = null; //浅拷贝 try { father = (Father) super.clone(); father.son = (Son) son.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } return father; } } @Data @Builder class Son implements Cloneable { Long age; String name; int grade; @Override public Object clone() { //拷贝 try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } }
赋初值
1 public static Son son = Son.builder().age(new Long(30L)).name("大儿子").grade(100).build(); 2 public static Father father = Father.builder().age(new Long(50)).name(new StringBuilder("爸爸")).son(son).build();
深拷贝示例:
//深拷贝 private static void deepClone() { System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); //开始克隆 Father fatherInLaw = (Father) father.clone(); fatherInLaw.getSon().setAge(10L); fatherInLaw.getSon().setGrade(0); fatherInLaw.getSon().setName("继子"); fatherInLaw.setAge(new Long(80L)); fatherInLaw.setName(new StringBuilder("继父")); //修改前 System.out.println("==========浅拷贝后==========="); System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); }
结果
父亲年龄:50 父亲姓名:爸爸 儿子年龄:30 儿子姓名:大儿子 儿子分数:100 ==========浅拷贝后=========== 父亲年龄:50 父亲姓名:爸爸 儿子年龄:30 儿子姓名:大儿子 儿子分数:100
(2)序列化方式
父子类
@Data @Builder class Father implements Serializable { Long age; StringBuilder name; Son son; public Object deepClone() throws IOException, ClassNotFoundException { //序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); //反序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } } @Data @Builder class Son implements Serializable { Long age; String name; int grade; }
实现方法
//深拷贝 private static void deepCloneV2() throws IOException, ClassNotFoundException { System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); //开始克隆 Father fatherInLaw = (Father) father.deepClone(); fatherInLaw.getSon().setAge(10L); fatherInLaw.getSon().setGrade(0); fatherInLaw.getSon().setName("继子"); fatherInLaw.setAge(new Long(80L)); fatherInLaw.setName(new StringBuilder("继父")); //修改前 System.out.println("==========浅拷贝后==========="); System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); }
结果:
父亲年龄:50 父亲姓名:爸爸 儿子年龄:30 儿子姓名:大儿子 儿子分数:100 ==========浅拷贝后=========== 父亲年龄:50 父亲姓名:爸爸 儿子年龄:30 儿子姓名:大儿子 儿子分数:100
3、使用Apach Commons
3.1实现深拷贝,需要实现序列化接口,SerializationUtils.clone(T object);
父子类
@Data @Builder class Father implements Serializable{ Long age; StringBuilder name; Son son; } @Data @Builder class Son implements Serializable{ Long age; String name; int grade; }
实现方法
//深拷贝 private static void deepCloneCommons() throws IOException, ClassNotFoundException { System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); //开始克隆 Father fatherInLaw = (Father) SerializationUtils.clone(father); fatherInLaw.getSon().setAge(10L); fatherInLaw.getSon().setGrade(0); fatherInLaw.getSon().setName("继子"); fatherInLaw.setAge(new Long(80L)); fatherInLaw.setName(new StringBuilder("继父")); //修改前 System.out.println("==========浅拷贝后==========="); System.out.println("父亲年龄:" + father.getAge()); System.out.println("父亲姓名:" + father.getName()); System.out.println("儿子年龄:" + father.getSon().getAge()); System.out.println("儿子姓名:" + father.getSon().getName()); System.out.println("儿子分数:" + father.getSon().getGrade()); }
结果
父亲年龄:50 父亲姓名:爸爸 儿子年龄:30 儿子姓名:大儿子 儿子分数:100 ==========深拷贝后=========== 父亲年龄:50 父亲姓名:爸爸 儿子年龄:30 儿子姓名:大儿子 儿子分数:100
3.2使用工具类实现浅拷贝
BeanUtils.copyProperties(Object dest, Object orig)直接调用即可实现浅拷贝,BeanUtils.cloneBean(Object obj)最终也是调用的copyProperties方法。
今日学习笔记到此结束。
书于:2019/08/01/ 22:40分