zoukankan      html  css  js  c++  java
  • 浅谈关于java中的深浅拷贝

    一.浅拷贝(shallow copy)


    1.如何实现浅拷贝?

    Object类 是所有类的直接或间接父类,Object中存在clone方法,如下

    protected native Object clone() throws CloneNotSupportedException;

    如果想要使一个类的对象能够调用clone方法 ,则需要实现Cloneable接口, 并重写 clone方法:

    public class Student implements Cloneable{
    
    
        private int sno ;
        private String name;
    
        //getter ,setter 省略
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            Student s = null;
            try{
                s = (Student)super.clone();
            }catch (Exception e){
                e.printStackTrace();
            }
            return s;
        }
    
    }

    现在测试clone方法:

    @Test
        public void test04() throws CloneNotSupportedException {
            //创建Student对象
            Student s1 = new Student();
            s1.setSno(1);
            s1.setName("Rye");
    
            //通过clone 拷贝一个对象
            Student s2 = (Student)s1.clone();
    
            System.out.println("s1:"+s1);
            System.out.println("s2:"+s2);
            System.out.println("s1 == s2 ? ==> "+(s1 == s2));
        }

    按照预期,克隆出的对象s2中的字段值应该与s1相同,但与s1对应的对象不在同一块内存空间,结果如下:

    s1:Student{sno=1, name='Rye'}
    s2:Student{sno=1, name='Rye'}
    s1 == s2 ? ==> false
    View Code

    此时如果修改 s1中的sno为2,那么会不会影响到s2中的sno呢?

    //修改s1中的sno
    s1.setSno(2);

    结果如下:

    s1:Student{sno=2, name='Rye'}
    s2:Student{sno=1, name='Rye'}
    View Code

    此时看似已经完成了 copy, s1 与 s2有着自己不同的值,但如果为Student中新增了Teacher类型的成员变量,结果还是跟上面一样吗?让我们改造下代码:

    public class Teacher {
    
        private int tno;
        private String name;
      
        //getter setter省略...  
    }
    public class Student  implements Cloneable{
    
        private int sno ;
        private String name;
        private Teacher teacher;
    
        //getter ,setter ,toString 省略...
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            Student s = null;
            try{
                s = (Student)super.clone();
            }catch (Exception e){
                e.printStackTrace();
            }
            return s;
        }    
    }

    此时测试代码如下:

        @Test
        public void test02() throws CloneNotSupportedException {
            Student student1 = new Student();
            student1.setSno(1);
            student1.setName("Rye");
    
            Teacher teacher = new Teacher();
            teacher.setTno(1);
            teacher.setName("LinTong");
    
            student1.setTeacher(teacher);
    
            Student student2 = (Student)student1.clone();
    
            System.out.println("student1:"+student1);
            System.out.println("student2:"+student2);
            System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));
            System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));
    
        }

    运行结果如下:

    student1:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
    student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
    student1 == student2 ? ==> false
    student1.teacher == student2.teacher ? ==> true
    View Code

    由此可见,此时经过clone生成的student2, 与 student1.二者中的teacher字段, 指向同一块内存空间;

    那么可能会有人问,这会有什么影响吗? 

    我们拷贝的目的,更多的时候是希望获得全新并且值相同的对象,操作原始对象或拷贝的新对象,对彼此之间互不影响;

    此时我们修改student1中teacher的tno ,如下:

    //修改teacher中的 tno值为2
    student1.getTeacher().setTno(2);

    再次运行test:

    student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
    student2:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
    student1 == student2 ? ==> false
    student1.teacher == student2.teacher ? ==> true
    View Code

    此时发现,student2中的teacher的tno ,也跟着变化了.

    变化的原因是:通过student1执行clone时,基本类型会完全copy一份到student2对应对象内存空间中, 但是对于Teacher对象仅仅是copy了一份Teacher的引用而已.

    而student1 与 student2的引用 指向的是同一块堆内存,因此不论是通过student1或是student2修改teacher 都会影响另外一个;

    通过图会更直观一些:

    2.浅拷贝中引用类型的变量拷贝的是对象的引用 , 可通过如下思路解决:

    Teacher类中也覆写clone方法:

        @Override
        protected Object clone() {
            Teacher teacher = null;
            try {
                teacher = (Teacher)super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return teacher;
        }

    修改Student中的clone方法,如下:

        @Override
        public Object clone() {
            Student s = null;
            try{
                s = (Student)super.clone();
                Teacher t = (Teacher)this.teacher.clone();
                s.setTeacher(t);
            }catch (Exception e){
                e.printStackTrace();
            }
            return s;
        }

    此时再次运行test:

    student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
    student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
    student1 == student2 ? ==> false
    student1.teacher == student2.teacher ? ==> false
    View Code

    由此可见,在copy Student的同时 将Teacher也进行了修改,如图:

    目前来看是满足了我们的需求,但是如果Teacher类中,同样也有别的引用类型 的成员变量呢?

    那么就同样需要一直覆写clone方法,如果这个关系不是特多还可以接受,如果引用关系很复杂就会显得代码繁琐;

    此时应该使用序列化完成深度拷贝;

    二.深拷贝(deep copy)


    使用序列化完成深拷贝

    深拷贝是利用对象流,将对象序列化,再反序列化得出新的对象. 因此首先需要实现序列化接口,如下:

    public class Student implements Serializable{
    
        private static final long serialVersionUID = -2232725257771333130L;
    
        private int sno ;
        private String name;
        private Teacher teacher;
      //getter ,setter,toString()省略... }

    Teacher也要实现序列化接口:

    public class Teacher implements Serializable{
        private static final long serialVersionUID = 4477679176385287943L;
        private int tno;
        private String name;
      
     //getter ,setter,toString()省略...
    }

    工具方法:

      //工具方法
      public Object cloneObject(Object object) throws IOException, ClassNotFoundException {
            //将对象序列化
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(object);
    
            //将字节反序列化
            ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            Object obj = objectInputStream.readObject();
    
            return obj;
        }

    测试类:

       public void test05() throws IOException, ClassNotFoundException {
            Student student1 = new Student();
            student1.setSno(1);
            student1.setName("Rye");
    
            Teacher teacher = new Teacher();
            teacher.setTno(1);
            teacher.setName("LinTong");
    
            student1.setTeacher(teacher);
    
            Student student2 = (Student)cloneObject(student1);
            //修改teacher中的 tno值为2
            student1.getTeacher().setTno(2);
    
            System.out.println("student1:"+student1);
            System.out.println("student2:"+student2);
            System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));
            System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));
        }

    如果Teacher类或者Student类没有实现序列化接口,则执行时会报异常,如下:

    java.io.NotSerializableException: com.example.test.Teacher

    在都实现了Serializable接口的情况下,运行结果如下:

    student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}}
    student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}}
    student1 == student2 ? ==> false
    student1.teacher == student2.teacher ? ==> false
    View Code

    由此通过对象流的方式,成功完成了深度拷贝;


    三.重写clone方法 与 通过序列化 两种拷贝方式比较:

    clone方法:

    优点:速度快,效率高

    缺点:在对象引用比较深时,使用此方式比较繁琐

    通过序列化:

    优点:非常简便的就可以完成深度copy

    缺点:由于序列化的过程需要跟磁盘打交道,因此效率会低于clone方式

    如何抉择?

    实际开发中,根据两种方式的优缺点进行选择即可!

  • 相关阅读:
    [CareerCup] 1.4 Replace Spaces 替换空格
    [CareerCup] 1.3 Permutation String 字符串的排列
    Android @1x,@2x,@3x 资源文件自动分包工具
    Android 使用xml实现边框阴影,背景渐变效果(附有RGB颜色查询对照表)
    Android中快速实现自定义字体!
    Android AS升级3.1 编译报错:The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin.
    Android getRunningTasks和getRunningAppProcesses失效
    Android 实现朋友圈有图片和视频
    Android 仿微信朋友圈拍小视频上传到服务器
    Android setUserVisibleHint-- fragment真正的onResume和onPause方法
  • 原文地址:https://www.cnblogs.com/lzzRye/p/9459465.html
Copyright © 2011-2022 走看看