深克隆与浅克隆 Java clone – deep and shallow copy

可以只看这一篇博客:https://blog.csdn.net/jeffleo/article/details/76737560 

深克隆:属性中指向的对象也会克隆

Java中定义的clone没有深浅之分,都是统一的调用Object的clone方法。为什么会有深克隆的概念?是由于我们在实现的过程中刻意的嵌套了clone方法的调用。也就是说深克隆就是在需要克隆的对象类型的类中全部实现克隆方法

1.Address类实现Cloneable接口,重写clone方法;

    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
2.在Customer类的clone方法中调用Address类的clone方法。


    @Override
    public Customer clone() throws CloneNotSupportedException {
        Customer customer = (Customer) super.clone();
        customer.address = address.clone();
        return customer;
    }
修改后测试代码的输出结果:

customer1:Customer[ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]

customer2:Customer[ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]

发现customer2无论如何修改,customer1都没有受到影响。
 

深克隆代码如下: 

补充:一个java源文件最多包含一个public类,但可以包含多个类,如下面代码 

扫描二维码关注公众号,回复: 6060307 查看本文章
public class Tsee {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("CH", "SD", "QD");
        Customer customer1 = new Customer(1, 23, address);
        Customer customer2 = customer1.clone();
        customer2.getAddress().setCity("JN");
        customer2.setID(2);
        System.out.println("customer1:" + customer1.toString());
        System.out.println("customer2:" + customer2.toString());
    }
}

class Customer implements Cloneable{
    public int ID;
    public int age;
    public Address address;
    public int getID() {
        return ID;
    }
    public void setID(int iD) {
        ID = iD;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public Customer(int iD, int age, Address address) {
        super();
        ID = iD;
        this.age = age;
        this.address = address;
    }
    @Override
    public String toString() {
        return "Customer [ID=" + ID + ", age=" + age + ", address=" + address
                + "]";
    }

    //在Customer类的clone方法中调用Address类的clone方法
    @Override
    public Customer clone() throws CloneNotSupportedException {
       Customer customer= (Customer) super.clone();
       customer.address=address.clone();
        return customer;
    }
}
class Address implements  Cloneable{
    private String country;
    private String province;
    private String city;
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getProvince() {
        return province;
    }
    public void setProvince(String province) {
        this.province = province;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [country=" + country + ", province=" + province
                + ", city=" + city + "]";
    }
    public Address(String country, String province, String city) {
        super();
        this.country = country;
        this.province = province;
        this.city = city;
    }
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}


区分:是否在类中具有引用类型的属性

若要看细节,也可以看看下面

1.  “存在一些设计缺陷,其中最大的缺点是Cloneable接口没有克隆方法。这意味着它根本不起作用:制作一些Cloneable并没有说明你可以用它做什么。相反,它说明了内部可以做些什么。它说如果通过反复调用super.clone它最终调用Object的clone方法,这个方法将返回原始的字段副本。“

“There are a few design flaws, the biggest of which is that the Cloneable interface does not have a clone method. And that means it simply doesn’t work: making something Cloneable doesn’t say anything about what you can do with it. Instead, it says something about what it can do internally. It says that if by calling super.clone repeatedly it ends up calling Object’s clone method, this method will return a field copy of the original.”

2.“If I were to be God at this point, and many people are probably glad I am not, I would say deprecate Cloneable and have a Copyable, because Cloneable has problems. Besides the fact that it’s misspelled, Cloneable doesn’t contain the clone method. That means you can’t test if something is an instance of Cloneable, cast it to Cloneable, and invoke clone. You have to use reflection again, which is awful. That is only one problem, but one I’d certainly solve.”

“如果我此时成为上帝,很多人可能很高兴我不是,我会说弃用Cloneable并有一个Copyable,因为Cloneable有问题。除了拼写错误之外,Cloneable不包含克隆方法。这意味着您无法测试某些内容是否是Cloneable的实例,将其强制转换为Cloneable,并调用clone。你必须再次使用反射,这很糟糕。这只是一个问题,但我肯定会解决。“

参考:https://howtodoinjava.com/java/cloning/cloneable-interface-is-broken-in-java/

1) 无论目标类是否实现了Cloneable接口,只要调用到了Object.clone(),比如通过super.clone(),那么就必须处理或者抛出CloneNotSupportedException,因为Object.clone()有throws这个异常,有抛的就必然有接的。

2) Object.clone()按照如下步骤执行:
  (1) 检查执行此方法的当前类有没有应用Clonable接口,如果没有,抛出CloneNotSupportedException异常。
  (2) 如果当前类有应用Clonable接口,则为当前类创建一个新对象,并将原对象中的所有字段进行一次浅层拷贝(通过赋值进行)。所以如果一个目标类应用了Clonable接口但并未重写clone()方法,它“看起来”仍然可以克隆。为什么是“看起来”下面会解释。

3) 为什么应用了Cloneable接口的类通常还必须重写一个public的clone()方法?这里有两个原因:
  (1) 如果不重写,由于Object.clone()是proteced属性,所以这个clone()方法将无法在外部被调用,更精确地说,无法在目标类之外的任何地方调用。这样就使得克隆失去了用武之地。
  (2) Object.clone()毕竟只是提供了浅层拷贝,对于基本类型的字段,可以说它成功克隆了。但对于对象型字段,它并没有实现克隆的功能,仅仅做了一个赋值。试运行一下下面的代码就会更清楚了:

public class Student implements  Cloneable {
    private int id;
    private String name;
    public StringBuffer sb = new StringBuffer("");

    public Student() {
        this.id = 744;
        this.name = "FL";
    }

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public boolean equals(Object obj) {
        return this.id == ((Student) obj).id;
    }

    public String toString() {
        return "Student id : " + id + " Name " + name;
    }

    public static void main(String[] args) throws CloneNotSupportedException // 这里为什么一定得写
    {
        Student s1 = new Student(101, "WangQiang");
        Student s2 = (Student) s1.clone();// 如果当前类有应用Clonable接口,则为当前类创建一个新对象,
        // 并将原对象中的所有字段进行一次浅层拷贝
        //对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
        System.out.println(s1 == s2);// out :false
        System.out.println(s1);//out:Student id : 101 Name WangQiang
        System.out.println(s2);//out: Student id : 101 Name WangQiang

        s1.sb.append("s1's string");
        System.out.println("s2.sb's value = " + s2.sb.toString());//out :s2.sb's value = s1's string
        System.out.println(s1.sb==s2.sb);//true 

    }


}

 Student s2 = (Student) s1.clone();// 如果当前类有应用Clonable接口,则为当前类创建一个新对象
        // 并将原对象中的所有字段进行一次浅层拷贝

System.out.println(s1); 打印s1 指向的对象的内容

  System.out.println(s1.sb==s2.sb);//true 
        //对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

sb为引用变量,指向一个对象。  

以上例子运行显示,不重写Object.clone()方法的话在目标类内部仍然可以调用目标类.clone(),但浅层拷贝导致s1和s2共享同一个StringBuffer对象,这是很危险的

4) 运行时是否抛出CloneNotSupportedException跟是否重写了clone()方法没有关系,只跟是否应用了Cloneable接口有关系。

5) 重写clone()方法不一定需要应用Cloneable接口,因为只有执行Object.clone()方法才会做这个检查。但如果重写的clone()方法中调用了super.clone(),那就必须应用Cloneable接口,原因不难理解。

6)应用Cloneable接口的好处在于,它可以允许你安全地调用super.clone(),从而快速地产生一个浅拷贝,之后只需要在重写的公共clone()方法中修改必须修改的字段,如那些不允许共享实例的对象。
参考:https://blog.csdn.net/alexandertech/article/details/6612577 

上图来源:https://www.cnblogs.com/liqiangchn/p/9465186.html 

猜你喜欢

转载自blog.csdn.net/nsjlive/article/details/89338457