刨析Object中的clone方法,涉及浅拷贝,深拷贝,原型模式
刨析Object中的clone方法,涉及浅拷贝,深拷贝,原型模式
说到java中的克隆,必要的一个方法就是Object类中native clone方法。
protected native Object clone() throws CloneNotSupportedException;
它是Object中的方法,这意味所有的类都可以实现这一方法。因为所有的类都隐式继承了Object。
注意:对象想要使用clone()必须要继承Cloneable接口.否则将抛出CloneNotSupportedException异常.
它一般在什么场景下使用呢?
比如说我们需要许多的奖状,同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,
难道我们要一个一个new 奖状(“XX”,msg); 这样一系列操作吗?
在参数较少的情况下,麻烦程度可能不太显现,那如果一个对象的参数设置就有许多,那就太麻烦了.
这时我们就可以使用clone方法快捷的创建对象。
就像这样:
Citation c1 = new Citation();Student stu = new Student("张三", "西安");c1.setStu(stu);//复制奖状Citation c2 = c1.clone();//获取c2奖状所属学生对象Student stu1 = c2.getStu();stu1.setName("李四");
我们只需要复制多个“三好学生”奖 状出来,然后在修改奖状上的名字即可。
所以对于对象的创建非常复杂,我们可以使用clone()快捷的创建对象。
这也就是设计模式中原型模式的思想.
使用clone 和 浅克隆
举个例子:
我们有一个Man对象,继承Cloneable接口,重写clone方法,并使用clone方法复制.
class Man implements Cloneable { String name; int age;Son son; //一个对象 其类拥有name和age属性 public Man(String name, int age, Son son) { this.age = age; this.name = name; this.son = son; }...省略name和age,son的get set方法 和重写的toString方法 //重写clone方法 @Override public Object clone() throws CloneNotSupportedException { return super.clone(); }}
Main方法:
public static void main(String[] args) throws CloneNotSupportedException {Son son = new son("小明",12); Man man1 = new Man("明他爸",35,son);//new 一个Man对象 Man man2 = (Man) man1.clone(); //使用clone Man man3 = (Man) man1.clone(); System.out.println(man1); //打印对象 System.out.println(man2); System.out.println(man3); System.out.println(man1==man2);//判断是否是同一个对象 System.out.println(man2==man3); System.out.println(man1.son==man2.son);//判断son属性对象地址是否相同 System.out.println(man2.son==man3.son);}
结果:
Man{name='明他爸', age=35, son=Son{name='小明', age=12}} //属性值相同Man{name='明他爸', age=35, son=Son{name='小明', age=12}}Man{name='明他爸', age=35, son=Son{name='小明', age=12}}false //Main对象地址不相同falsetrue //son属性对象地址相同true
可以看到使用clone()复制的对象,它们的属性值相等,但是对象地址不相同,意味这他们拥有不同的堆空间.不是同一个对象.
但是Main类中属性Son对象却是同一个对象.他们有相同的地址.
这也就是浅克隆.
结论:单纯使用clone即super.clone();创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有 属性所指向的对象的内存地址.
向上面使用浅克隆,复制出许多不同Man对象,而son对象却只有一个. 这么多爸爸只有一个儿子,那可就太惨了.🤣🤣🤣
当我修改对象中的非基本类型属性时,修改一个将会影响所有的克隆对象,因为对象中的非基本类型属性都是同一个.这将是一个非常严重的安全问题.
怎么解决呢?这就需要深克隆了.
深克隆是完完全全的克隆.复制整个对象信息,包含值类型和引用类型
两种深克隆的实现
深克隆的实现通常有两种方法:
- 序列化实现深克隆:先将原对象序列化到内存的字节流中,再从字节流中反序列化出刚刚存储的对象,这个新对象和原对象就不存在任何地址上的共享,这样就实现了深克隆。
- 所有引用类型都实现克隆:要复制对象的所有引用类型都要实现克隆,所有对象都是复制的新对象,从而实现了深克隆。
深克隆实现方式一:序列化(推荐)
实现思路:先将要拷贝对象写入到内存中的字节流中,然后再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,那么这个新对象和原对象就不存在任何地址上的共享,自然实现了深拷贝。
注意类要实现 Serializable 接口
请参考以下代码:
class CloneTest { public static void main(String[] args) throws CloneNotSupportedException { BirdChild birdChild = new BirdChild(); birdChild.name = "小小鸟"; Bird bird = new Bird(); bird.name = "小鸟"; bird.birdChild = birdChild; // 使用序列化克隆对象 Bird bird2 = CloneUtils.clone(bird); bird2.name = "黄雀"; bird2.birdChild.name = "小黄雀"; System.out.println("bird name:" + bird.name); System.out.println("bird child name:" + bird.birdChild.name); System.out.println("bird name 2:" + bird2.name); System.out.println("bird child name 2:" + bird2.birdChild.name); }}class CloneUtils { public static <T extends Serializable> T clone(T obj) { T cloneObj = null; try { //写入字节流 ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bo); oos.writeObject(obj); oos.close(); //分配内存,写入原始对象,生成新对象 ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流 ObjectInputStream oi = new ObjectInputStream(bi); //返回生成的新对象 cloneObj = (T) oi.readObject(); oi.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; }}
程序执行结果:
bird name:小鸟bird child name:小小鸟bird name 2:黄雀bird child name 2:小黄雀
深克隆实现方式二:所有引用类型都实现克隆
学生类:
public class Student implements Cloneable { private String name; private int age; public Student(String name, int age){ this.name = name; this.age = age; } @Override protected Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } ...省略Student 的set get方法}
老师类:
public class Teacher implements Cloneable{ private String name; private int age; private Student student; public Teacher(String name, int age, Student student){ this.name = name; this.age = age; this.student = student; } ...省略Teacher类get set方法 // 覆盖 @Override public Object clone() { Teacher t = null; try { t = (Teacher) super.clone(); t.student = (Student)student.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return t; } }
测试:
public class test { public static void main(String[] args) { Student s = new Student("学生1", 11); Teacher origin = new Teacher("老师原对象", 23, s); System.out.println("克隆前的学生姓名:" + origin.getStudent().getName()); Teacher clone = (Teacher) origin.clone();//调用克隆方法 // 更改克隆后的学生信息 更改了姓名 clone.getStudent().setName("我是克隆对象更改后的学生2"); System.out.println("克隆后的学生姓名:" + clone.getStudent().getName()); }}
运行结果:
克隆前的学生姓名:学生1克隆后的学生姓名:我是克隆对象更改后的学生2
总结
- 浅克隆:只会复制对象的值类型,而不会复制对象的引用类型;
- 深克隆:复制整个对象,包含值类型和引用类型。
- 克隆可分为浅克隆和深克隆,实际应用中一般使用深克隆
- 深克隆有两种实现方法:
- 使用序列化(推荐)
- 所有引用类型都实现克隆
- 使用clone()方法需要实现Cloneable接口,否则将抛出CloneNotSupportedException异常.
- 原型模式就是使用克隆,快捷创建对象.
- Object中clone()方法是一个native 方法,也就是原生函数,本地方法,使用操作系统底层的语言实现的,因此执行效率更高
扩展
Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
还有什么问题小伙伴们可以留言讨论