Java基础笔记 – 对象的深复制与浅复制 实现Cloneable接口实现深复制 序列化实现深复制
本文由发表于6年前 | Java基础 | 评论数 5 |  被围观 16,894 views+
深复制与浅复制:浅复制(shallow clone):深复制(deep clone):Object中的clone方法:Object的clone方法的说明:Java中实现对象的克隆:下面是一个实现深复制的例子:使用序列化来进行对象的深复制:以下是实现过程描述:下面是使用序列化实现深复制的例子:关于Serializable接口的类中的serialVersionUID:serialVersionUID主要是为了解决对象反序列化的兼容性问题。Marker Interface:
深复制与浅复制:
浅复制(shallow clone):

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

深复制(deep clone):

被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

Object中的clone方法:
clone
protected Object clone()
                throws CloneNotSupportedException
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,表达式: 
        x.clone() != x
为 true,表达式: 
       x.clone().getClass() == x.getClass()
也为 true,但这些并非必须要满足的要求。一般情况下: 
       x.clone().equals(x)
为 true,但这并非必须要满足的要求。 
    按照惯例,返回的对象应该通过调用 super.clone 获得。如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。

按照惯例,此方法返回的对象应该独立于该对象(正被复制的对象)。要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行修改。这通常意味着要复制包含正在被复制对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改 super.clone 返回的对象中的字段。

Object 类的 clone 方法执行特定的复制操作。首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。注意,所有的数组都被视为实现接口 Cloneable。否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段;这些字段的内容没有被自我复制。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。

Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。

Object的clone方法的说明:

在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。

继承自java.lang.Object类的clone()方法是浅复制。

Java中实现对象的克隆:
①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
②在派生类中覆盖基类的clone()方法,并声明为public。
③在派生类的clone()方法中,调用super.clone()。
④在派生类中实现Cloneable接口(一个标识性的接口)。
下面是一个实现深复制的例子:

创建Employer类,实现Cloneable接口:

class Employer implements Cloneable{
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

创建Employee类,实现Cloneable接口,并改写clone方法,实现深复制:

class Employee implements Cloneable{
    private String username;
    private Employer employer;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Employer getEmployer() {
        return employer;
    }
    public void setEmployer(Employer employer) {
        this.employer = employer;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        //克隆Employee对象并手动的进一步克隆Employee对象中包含的Employer对象
        Employee employee = (Employee)super.clone();
        employee.setEmployer((Employer) employee.getEmployer().clone());
        return employee;
    }
}

这样,在客户端拷贝的两个Employee对象的Employer就互不影响了:

public static void main(String[] args) throws CloneNotSupportedException {
    Employer employer = new Employer();
    employer.setUsername("arthinking");

    Employee employee = new Employee();
    employee.setUsername("Jason");
    employee.setEmployer(employer);

    //employee2由employee深复制得到
    Employee employee2 = (Employee) employee.clone();
    //这样两个employee各自保存了两个employer
    employee2.getEmployer().setUsername("Jason");
    System.out.println(employee.getEmployer().getUsername());
    System.out.println(employee2.getEmployer().getUsername());
}
使用序列化来进行对象的深复制:

序列化即是把对象写到流里面的过程;反序列化即是把对象从流中读取出来的过程。写在流里的是对象的一个拷贝,而原来的对象仍然在JVM里面。

以下是实现过程描述:

前提是对象以及对象内部所有用到的对象都是可序列化的,否则就需要考虑把那些不可序列化的对象标记为transient,从而把它排除到复制范围之外。

然后使对象实现Serializable接口。

把对象写入到一个流里(不用依赖于文件,直接暂存在内存中即可),在从流里读取出来,便得到了一个深复制的对象。

下面是使用序列化实现深复制的例子:

创建Employer2类实现序列化接口:

class Employer2 implements Serializable{

    private static final long serialVersionUID = 1L;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

创建Employee2类实现序列化接口,并通过序列化编写深复制的方法:

class Employee2 implements Serializable{

    private static final long serialVersionUID = 3969438177161438988L;
    private String name;
    private Employer2 employer;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Employer2 getEmployer() {
        return employer;
    }
    public void setEmployer(Employer2 employer) {
        this.employer = employer;
    }
    /**
     * 实现深复制的方法
     */
    public Object deepCopy() throws IOException, ClassNotFoundException{
        //字节数组输出流,暂存到内存中
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //序列化
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        //反序列化
        return ois.readObject();
    }
}

在main方法中使用序列化深复制对象:

public static void main(String[] args) throws IOException, ClassNotFoundException {
    Employer2 employer = new Employer2();
    employer.setName("arthinking");
    Employee2 employee = new Employee2();
    employee.setName("Jason");
    employee.setEmployer(employer);
    //通过深复制创建employee2
    Employee2 employee2 = (Employee2) employee.deepCopy();
    employee2.getEmployer().setName("Jason");

    System.out.println(employee.getEmployer().getName());
    System.out.println(employee2.getEmployer().getName());
}
关于Serializable接口的类中的serialVersionUID:

serialVersionUID是long类型的。在Eclipse中有两种生成方式:

默认的是1L:

private static final long serialVersionUID = 1L;

另外一个则是根据类名、接口名、成员方法以及属性等生成一个64位的哈希字段:

private static final long serialVersionUID = 3969438177161438988L;
serialVersionUID主要是为了解决对象反序列化的兼容性问题。

如果没有提供serialVersionUID,对象序列化后存到硬盘上之后,再增加或减少类的filed。这样,当反序列化时,就会出现Exception,造成不兼容问题。

但当serialVersionUID相同时,它就会将不一样的field以type的缺省值反序列化。这样就可以避开不兼容问题了。

Marker Interface:

标识接口,没有定义任何的方法,如Cloneable和Serializable接口。

除了文章中有特别说明,均为IT宅原创文章,转载请以链接形式注明出处。
本文链接:http://www.itzhai.com/java-based-notebook-the-object-of-deep-and-shallow-copy-copy-copy-implement-the-cloneable-interface-serializing-deep-deep-copy.html
arthinking Java技术交流群:280755654,入门群:428693174 more
分享到:
 
2011 10/25
文章评论
    5条评论
  1. anheitongyi 2012年07月31日20:33:01  #-49楼 回复 回复

    deep clone是深度克隆,shallow clone是浅克隆,实现clonealbe接口的是浅克隆

    • cici1224 2012年11月02日10:56:39 回复 回复

      楼上是正解 估计是一不小心弄错了

      • arthinking 2012年11月02日17:26:52 回复 回复

        还真是写错了。 Thanks~

  2. iceout 2012年11月07日16:27:20  #-48楼 回复 回复

    “创建Employee类,实现Cloneable接口,并改写clone方法,实现深复制:”是不对的,String是不可变的,所以造成了实现deep clone的假象。。

    • iceout 2012年11月07日17:39:17 回复 回复

      我错了。。原意指Employer包含list之类的,仔细想想不应该算Employee的错,在Employer改写clone就行了

给我留言

有人回复时邮件通知我
Java基础的相关文章
随机文章 本月热门 热评
1 分享两款Javascript在线IDE 2013/1/11
2 Java基础笔记 – 数组 二维数组 三维数组 Arrays类 2011/10/29
3 HTTP协议学习 – HTTP HTTP协议介绍 持续连接 HTTP消息 2011/11/6
4 总体设计-模块化 层次图 结构图 2011/7/1
5 ubuntu下安装Bochs虚拟机的方法及其可能遇到的make问题 2011/4/29
6 Android工程目录结构的说明 创建工程 AndroidManifest.xml 2011/7/12
友情推荐 更多
破博客 文官洗碗安天下,武将打怪定乾坤。多么美好的年代,思之令人泪落。
Mr.5's Life 白天是一名程序员,晚上就是个有抱负的探索者
行知-追寻技术之美 关注大数据,分布式系统
我爱编程 编程成长轨迹
Cynthia's Blog 学习笔记 知识总结 思考感悟
 
欢迎关注我的公众号 IT宅
关于IT宅 文章归档

IT宅中的文章除了标题注明转载或有特别说明的文章,均为IT宅的技术知识总结,学习笔记或随笔。如果喜欢,请使用文章下面提供的分享组件。转载请注明出处并加入文章的原链接。 感谢大家的支持。

联系我们:admin@itzhai.com

Theme by arthinking. Copyright © 2011-2015 IT宅.com 保留所有权利.