effective java(11) 之谨慎地覆盖clone

来源:互联网 发布:python unittest 回滚 编辑:程序博客网 时间:2024/05/22 15:49
effective java 之谨慎地覆盖clone


1、Cloneable接口表明这样的对象是允许克隆的,但这个接口并没有成功达到这个目的,主要是因为它缺少一个clone方法,Object的clone方法是受保护的。
如果不借助反射,就不能仅仅因为一个对象实现了Colneable就可以调用clone方法,即使是反射调用也不能保证这个对象一定具有可访问clone方法。

2、Cloneable并没有包含任何方法,那么它到底有什么用呢?
它其实决定了Object中受保护的clone方法实现的行为,如果一个类实现了Cloneable那么Object的clone方法就返回该对象的逐域拷贝,否则会抛出CloneNotSupportedException。
但真说接口一种极端非典型用法,不值得提倡。

3、通常情况下,实现接口是为了表明类可以为它的客户做些什么,然而对于Cloneable接口改变了超类中受保护的方法的行为。

4、如果实现Cloneable接口是要对某个类起到作用,类和它的所有超类都必须遵守一个一定协议,言外之意就是无需调用构造器就可以创建对象。

5、Object中Clone方法的通用约定:

Clone方法用于创建和返回对象的一个拷贝,一般含义如下:
1、对于任何对象x,表达式 x.clone()!=x 将会是true,并且表达式 x.clone().getClass() == x.getClass()将会是true,但这不是绝对要求。
2、通常情况下,表达式 x.clone.equals(x)将会是true,同1一样这不是绝对要求。
拷贝对象往往会导致创建它的类的一个新实例,但它同时也要求拷贝内部的数据接口,这个过程中没有调用构造器。


6、一个浅克隆的例子:
public class Student implements Cloneable {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public Object clone() {Object o = null;try {o = (Student) super.clone();// Object 中的clone()识别出你要复制的是哪个对象} catch (CloneNotSupportedException e) {System.out.println(e.toString());}return o;}public static void main(String[] args) {Student s1 = new Student("zhangsan", 18);Student s2 = (Student) s1.clone();System.out.println("克隆后s2:name=" + s2.name + "," + "age=" + s2.age);s2.name = "lisi";s2.age = 20;// 修改学生2后,不影响学生1的值。System.out.println("克隆修改后s1:name=" + s1.name + "," + "age=" + s1.age);System.out.println("克隆修改后s2:name=" + s2.name + "," + "age=" + s2.age);}}




克隆后s2:name=zhangsan,age=18
克隆修改后s1:name=zhangsan,age=18
克隆修改后s2:name=lisi,age=20


如果类的每个域包含一个基本类型的值,或者包含一个指向不可变对象的引用,那么被返回的对象则正是所需要的对象,只需要简单地调用super.clone() 而不用做进一步的处理。
但是如果类指向的其他对象的引用是可变的时,那么只是简单的clone就无法做到完全的克隆了。


7、对引用的克隆:
public class Student implements Cloneable {private String name;private int age;Professor p;// 这里学生1和学生2的引用值都是一样的。public Student(String name, int age, Professor p) {super();this.name = name;this.age = age;this.p = p;}public Object clone() {Object o = null;try {o = (Student) super.clone();// Object 中的clone()识别出你要复制的是哪个对象} catch (CloneNotSupportedException e) {System.out.println(e.toString());}return o;}public static void main(String[] args) {Professor p = new Professor("wangwu", 50);Student s1 = new Student("zhangsan", 18, p);Student s2 = (Student) s1.clone();System.out.println("克隆后s1:name=" + s1.p.name + "," + "age=" + s1.p.age);System.out.println("克隆后s2:name=" + s2.p.name + "," + "age=" + s2.p.age);s2.p.name = "lisi";s2.p.age = 30;// 修改克隆的值,原来的值也会变化,引用的是同一个值。System.out.println("克隆后s1:name=" + s1.p.name + "," + "age=" + s1.p.age);System.out.println("克隆后s2:name=" + s2.p.name + "," + "age=" + s2.p.age);}}class Professor {String name;int age;Professor(String name, int age) {this.name = name;this.age = age;}}

克隆后s1:name=wangwu,age=50
克隆后s2:name=wangwu,age=50
克隆后s1:name=lisi,age=30
克隆后s2:name=lisi,age=30

s2对s1进行克隆时,对s1的属性Professor p并没有进行克隆,导致s1和s2对其引用指向同一个,这会造成s2若改变了值,s1则也被动改变了。
如何做到修改s2的教授不会影响s1的教授。需要深度克隆。

9、深拷贝例子:
public class Student implements Cloneable {private String name;private int age;Professor p;// 学生1和学生2的引用值都是一样的。public Student(String name, int age, Professor p) {super();this.name = name;this.age = age;this.p = p;}public Object clone() {Student o = null;try {o = (Student) super.clone();// Object 中的clone()识别出你要复制的是哪个对象} catch (CloneNotSupportedException e) {System.out.println(e.toString());}o.p = (Professor) p.clone();return o;}public static void main(String[] args) {Professor p = new Professor("wangwu", 50);Student s1 = new Student("zhangsan", 18, p);Student s2 = (Student) s1.clone();System.out.println("克隆后s1:name=" + s1.p.name + "," + "age=" + s1.p.age);System.out.println("克隆后s2:name=" + s2.p.name + "," + "age=" + s2.p.age);s2.p.name = "lisi";s2.p.age = 30;// 修改学生2后,不影响学生1的值。System.out.println("克隆后s1:name=" + s1.p.name + "," + "age=" + s1.p.age);System.out.println("克隆后s2:name=" + s2.p.name + "," + "age=" + s2.p.age);}}class Professor implements Cloneable {String name;int age;Professor(String name, int age) {this.name = name;this.age = age;}public Object clone() {Object o = null;try {o = super.clone();} catch (CloneNotSupportedException e) {System.out.println(e.toString());}return o;}}

克隆后s1:name=wangwu,age=50
克隆后s2:name=wangwu,age=50
克隆后s1:name=wangwu,age=50
克隆后s2:name=lisi,age=30

修改s2的教授不会影响s1的教授。
因此,在使用clone时,一定要分清需要克隆的对象属性。


10、替代方法:
另一个实现对象拷贝的好办法是提供一个拷贝构造器(copy constructor)或拷贝工厂(copy factory)。
拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类,例如:
public Yum(Yum yum);
    拷贝工厂是类似于拷贝构造器的静态工厂:
public static Yum newInstance(Yun yum);
    拷贝构造器的做法,以及静态工厂方法的变形,都比Cloneable/clone方法具有更多的优势:
它们不依赖域某一种很有风险的,语言之外的对象创建机制;
它们不要求遵守尚未定制好的文档规范,不会与final域的正常使用发生冲突,它们不会抛出不必要的受检异常,不需要进行类型转换。
    更进一步,拷贝构造器或者拷贝工厂可以带一个参数,参数类型是通过该类实现的接口。
例如,按照管理,所有通用集合实现都提供了一个拷贝构造器,它的参数类型为Collection或者是Map。
基于接口的拷贝构造器和拷贝工厂,能允许用户选择拷贝的实现类型,而不是强迫客户接受原始的实现类型。
例如,现在有一个HashSet,并且希望把它拷贝成一个TreeSet。clone方法无法提供这样的功能,但是用拷贝构造器很容易实现,new TreeSet(s)。


如果想要了解更多有关深拷贝和浅拷贝的信息,可以去看设计模式原型模式那节,有非常清晰的介绍。


每天努力一点,每天都在进步。
原创粉丝点击