浅谈关于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

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

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

结果如下:

s1:Student{sno=2, name=‘Rye‘}
s2:Student{sno=1, name=‘Rye‘}

此时看似已经完成了 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

由此可见,此时经过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

此时发现,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

由此可见,在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

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



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

clone方法:

优点:速度快,效率高

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

通过序列化:

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

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

如何抉择?

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

原文地址:https://www.cnblogs.com/lzzRye/p/9459465.html

时间: 2024-11-09 08:43:13

浅谈关于java中的深浅拷贝的相关文章

浅谈对Java中ThreadLocal类的理解

首先要明确:ThreadLocal不是一个多线程类,或者应该叫做线程局部变量.这从ThreadLocal的JDK定义中就可以看到 public class ThreadLocal<T>extends Object 可以看出ThreadLocal只是一个普普通通的类,并没有继承自Thread或实现Runnable接口. 同时也可以看到ThreadLocal使用了泛型,这样他就可以操作几乎任何类型的数据了.下面说JDK API代码时具体再说. 对此类,看看JDK API中的部分描述: 该类提供了线

转: 浅谈C/C++中的指针和数组(二)

转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组的一些区别,然而在某些情况下,指针和数组是等同的,下面讨论一下什么时候指针和数组是相同的. C语言标准对此作了说明: 规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针: 注:下面几种情况例外 1)数组名作为sizeof的操作数 2)使用&取数组的地址 规则2:下标总是与指针的偏移量

Python中的深浅拷贝详解

要说明Python中的深浅拷贝,可能要涉及到下面的一系列概念需要简单说明下: 变量-引用-对象(可变对象,不可变对象)切片-拷贝-浅拷贝-深拷贝 [变量-对象-引用] 在Python中一切都是对象,比如说: 3, 3.14, 'Hello', [1,2,3,4],{'a':1}...... 甚至连type其本身都是对象,type对象 Python中变量与C/C++/Java中不同,它是指对象的引用 单独赋值: 比如说: >>> a = 3 在运行a=3后,变量a变成了对象3的一个引用.在

浅谈游戏开发中碰撞检测

原创整理不易,转载请注明出处:使用Memcached.Spring AOP构建数据库前端缓存框架 代码下载地址:http://www.zuidaima.com/share/1781569917635584.htm 数 据库访问可能是很多网站的瓶颈.动不动就连接池耗尽.内存溢出等.前面已经讲到如果我们的网站是一个分布式的大型站点,那么使用memcached实现数 据库的前端缓存是个很不错的选择:但如果网站本身足够小只有一个服务器,甚至是vps的那种,不推荐使用memcached,使用Hiberna

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

转:浅谈C/C++中的指针和数组(一)

转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242138.html 浅谈C/C++中的指针和数组(一) 指针是C/C++的精华,而指针和数组又是一对欢喜冤家,很多时候我们并不能很好的区分指针和数组,对于刚毕业的计算机系的本科生很少有人能够熟练掌握指针以及数组的用法和区别.造成这种原因可能跟现在大学教学以及现在市面上流行的很多C或者C++教程有关,这些教程虽然通俗易懂,但是在很多关键性的地方却避而不谈或者根本阐述不清楚,甚至很

浅谈C语言中的联合体(转载)

联合体union 当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union).在C Programming Language 一书中对于联合体是这么描述的: 1)联合体是一个结构: 2)它的所有成员相对于基地址的偏移量都为0: 3)此结构空间要大到足够容纳最"宽"的成员: 4)其对齐方式要适合其中所有的成员: 下面解释这四条描述: 由于联合体中的所有成员是共享一段内存的,因此每个成员的存放首地址相对于于联合体变量的基地址的偏移量为0,即所有成员的首地址都是一样的.为

浅谈深度学习中潜藏的稀疏表达

浅谈深度学习中潜藏的稀疏表达 “王杨卢骆当时体,轻薄为文哂未休. 尔曹身与名俱灭,不废江河万古流.” — 唐 杜甫<戏为六绝句>(其二) [不要为我为啥放这首在开头,千人千面千理解吧] 深度学习:概述和一孔之见 深度学习(DL),或说深度神经网络(DNN), 作为传统机器学习中神经网络(NN).感知机(perceptron)模型的扩展延伸,正掀起铺天盖地的热潮.DNN火箭般的研究速度,在短短数年内带来了能“读懂”照片内容的图像识别系统,能和人对话到毫无PS痕迹的语音助手,能击败围棋世界冠军.引

浅谈C语言中的强符号、弱符号、强引用和弱引用

摘自http://www.jb51.net/article/56924.htm 浅谈C语言中的强符号.弱符号.强引用和弱引用 投稿:hebedich 字体:[增加 减小] 类型:转载 时间:2014-10-31 我要评论 这篇文章主要介绍了C语言中的强符号.弱符号.强引用和弱引用的定义及相关内容,非常的简单易懂,有需要的朋友可以参考下 首先我表示很悲剧,在看<程序员的自我修养--链接.装载与库>之前我竟不知道C有强符号.弱符号.强引用和弱引用.在看到3.5.5节弱符号和强符号时,我感觉有些困惑