zoukankan      html  css  js  c++  java
  • Java的“影子克隆”和“深度克隆”

    今天来学习学习java对象的克隆,在写代码的时候,有时候我们会这样写:对象1=对象2,也就是把对象2赋值给对象1了,但是这样做有个问题,就是如果我们修改了对象2的属性值,对象1的相同属性值也被修改了,反过来亦如此,让我来证明一下:

    public class A {
        private String msg;
    
        public A() {
            msg = "Hello";
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
    public class Main {
    
        public static void main(String[] args) {
            A a1 = new A();
            A a2 = new A();
            System.out.println("a1.msg: " + a1.getMsg());
            a1 = a2;
            a2.setMsg("World");
            System.out.println("a1.msg: " + a1.getMsg());
        }
    
    }

    运行结果:

    从Main类可以看到,一开始我们new了两个对象,也就是在JVM堆内存开辟了两块内存空间,接着把a2赋值给a1,这就意味着a2和a1对象指向了栈内存中同一个引用,这个时候我们修改a2对象的属性,a1对象的属性也同时会改变,反过来亦如此,例如我现在修改a1的属性来试试看a2的属性是否被改变了:

    public class Main {
        public static void main(String[] args) {
            A a1 = new A();
            A a2 = new A();
            System.out.println("a1.msg: " + a1.getMsg());
            a1 = a2;
            a1.setMsg("Java");
            System.out.println("a2.msg: " + a2.getMsg());
        }
    }

    毫无悬念地改变了,也就是说使用“=”操作符会让两个对象指向同一个引用,两个对象的属性值是互通的,一个对象的改变必然会同步到另一个对象上面,但是有时候我们并不想一个对象的改变引起另一个对象的改变,我们希望a2对象被创建后含有a1对象初始的值,但是a2对象属性的改变不会影响a1对象,这样我们就应该使用对象的克隆,对象的克隆又分浅克隆和深克隆,我们先来看看浅克隆的实现方式:

    public class A implements Cloneable {
        private String msg;
    
        public A() {
            msg = "Hello";
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
        
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    public class Main {
        public static void main(String[] args) throws CloneNotSupportedException {
            A a1 = new A();
            System.out.println("a1.msg: " + a1.getMsg());
            A a2 = (A) a1.clone();
            System.out.println("a2.msg: " + a2.getMsg());
            a2.setMsg("Java");
            System.out.println("a1.msg: " + a1.getMsg());
            System.out.println("a2.msg: " + a2.getMsg());
        }
    }

    可以看到实现对象的克隆比较简单,首先该类应该实现Cloneable接口并覆盖Object根类的clone方法,注意Cloneable只是一个标记接口,里面并没有定义任何方法,clone方法里面的实现也很简单,调用父类Object的clone方法就好了。现在看看Main类就知道,a2对象的msg初始化是"Hello",把a2的msg属性的值改为"Java"后,并没有影响a1对象的msg值,其值仍然是"Hello". 我们刚才看到的是浅克隆,没有集合属性,如果有集合属性的话,浅克隆就应付不了这种情况了,我们来看看代码:

    public class A implements Cloneable {
        private List<String> list;
    
        public A() {
            list = new ArrayList<String>();
            list.add("Hello");
            list.add("World");
        }
        
        public List<String> getList() {
            return list;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    public class Main {
        public static void main(String[] args) throws CloneNotSupportedException {
            A a1 = new A();
            System.out.println("a1.list: " + a1.getList());
            A a2 = (A) a1.clone();
            System.out.println("a2.list: " + a2.getList());
            a2.getList().add("Java");
            System.out.println("a1.list: " + a1.getList());
            System.out.println("a2.list: " + a2.getList());
        }
    }

    大家看看,神奇的魔咒又出现了,即使我们实现了对象的克隆,但是因为对象的属性是集合类,这样我们修改a2对象的集合类中的值,也一定会影响a1对象集合类的值, 这是因为a1和a2对象的list属性是指向同一个内存地址的,面对这种情况,我们需要使用到对象的深克隆,具体怎么实现呢,来看代码:

    public class A implements Cloneable {
        private List<String> list;
    
        public A() {
            list = new ArrayList<String>();
            list.add("Hello");
            list.add("World");
        }
        
        public List<String> getList() {
            return list;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        @Override
        protected A clone() throws CloneNotSupportedException {
            A other = (A) super.clone();
            List<String> otherList = new ArrayList<String>();
            for (String s : list) {
                otherList.add(s);
            }
            other.setList(otherList);
            return other;
        }
    }
    public class Main {
        public static void main(String[] args) throws CloneNotSupportedException {
            A a1 = new A();
            System.out.println("a1.list: " + a1.getList());
            A a2 = (A) a1.clone();
            System.out.println("a2.list: " + a2.getList());
            a2.getList().add("Java");
            System.out.println("a1.list: " + a1.getList());
            System.out.println("a2.list: " + a2.getList());
        }
    }

    大家可以看到,深克隆的实现也就是要在clone方法里面new一个list,并把原来对象list中的值赋值到新list中并把新list设置到新对象,说起来好拗口啊,这样a2.list值得改变不会影响a1.list了,想想如果原始类的属性是一个对象,而对象的属性又有对象和集合类,那么clone方法里面的代码就会很大,因为深克隆必须保证全面的克隆。

  • 相关阅读:
    使用积分图像统计元素
    compareHist
    均值平移算法
    课后作业-阅读任务-阅读提问-1
    《对软件工程课程的期望》
    自我介绍
    作业
    结对-结对编项目作业名称-需求分析
    对软件工程课程的期望
    自我介绍
  • 原文地址:https://www.cnblogs.com/stonefeng/p/5808178.html
Copyright © 2011-2022 走看看