Java中的浅拷贝与深拷贝

Object中的clone方法:

protected native 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接口。

原文链接: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

时间: 2024-08-02 23:02:36

Java中的浅拷贝与深拷贝的相关文章

Java基础之浅拷贝与深拷贝

含义 浅拷贝:进对对象本身(包括对象中的基本变量)进行拷贝,而不拷贝对象包含的引用指向的对象. 深拷贝:不仅对对象本身,而且还对对象所包含的引用指向的对象进行拷贝. 深拷贝可以看做是对浅拷贝的递归. 举例来说:对象A1中包含对B1的引用,B1中包含对C1的引用.浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用.深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用. 克隆方法clone() p

Java对象的浅拷贝和深拷贝&&String类型的赋值

Java中的数据类型分为基本数据类型和引用数据类型.对于这两种数据类型,在进行赋值操作.方法传参或返回值时,会有值传递和引用(地址)传递的差别. 浅拷贝(Shallow Copy): ①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象.因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据. ②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组.某个类的对象等,那么浅拷贝会进行引用传递,也就是只

Javascript中的浅拷贝和深拷贝

很多开发语言中都有浅拷贝和深拷贝的说法,这里简单区分一下它们在Javascript中的区别,以及jQuery中深拷贝的实现. 在谈浅拷贝和深拷贝之前,先要屡清楚Javascript中的按值访问和按引用访问这两个概念. 按值访问是针对基本类型(string.number.boolean.null.undefined).基本类型以值的形式被存放在栈内存中,我们通过变量操作的是栈内存中实际的值. 按引用访问时针对引用类型(Object.Array.Date.RegExp.Function).引用类型以

C#中的浅拷贝和深拷贝

#中有两种类型变量,一种是值类型变量,一种是引用类型变量. 对于前者,copy是属于全盘复制:而对于后者,一般的copy只是浅copy,只copy引用地址,相当于只传递一个引用指针一样.因此对于后者进行真正copy的时候,也是最费事的,具体的说,必须为其实现 ICloneable接口中提供的Clone方法,从而产生一个全新的对象. 浅拷贝(影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用. 深拷贝(深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的.

浅谈JS中的浅拷贝与深拷贝

前端工程师应该都比较熟悉浅拷贝和深拷贝的概念,在日常业务代码的过程中,特别是做数据处理的时候,经常行的会遇到,比如如何在不修改原对象的基础上,重新生成一个一模一样的对象,加以利用,又或是,如何巧妙地运用相关的内置API,来达成自己所需要的结果,比如数组相关的操作,splice和slice就是截然相反的处理,虽然同样是对数组进行截取操作,但是前者会影响原数组,后者则是返回一个新的数组对象,而对原来的数组并不会产生任何影响,这其中的差别,需要有一定的开发经验才能明白. 好了,废话也不多说,下面来简单

Objective-C中的浅拷贝和深拷贝详解

  浅拷贝 浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间.如: 1 2 char* str = (char*)malloc(100); char* str2 = str; 浅拷贝只是对对象的简单拷贝,让几个对象共用一片内存,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针. iOS 里面的浅拷贝: 在 iOS 里面, 使用retain 关键字进行引用计数,就是一种更加保险的浅拷贝.他既让几个指针共用同一片内存空间,又可以在release

Python中的浅拷贝与深拷贝

以下都是参考资料后,我自己的理解,如有错误希望大家不吝赐教. 大家有没有遇到这样一种情况,对象赋值后,对其中一个变量进行修改,另外一个变量的值也改变了.比如: person = ['name', ['saving', 100]] hubby = person wifey = person hubby[0] = 'joe' wifey[0] = 'jane' print hubby, wifey hubby[1][1] = 50 print hubby, wifey >>>['jane',

Objective-C中的浅拷贝和深拷贝

浅拷贝 浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间.如: char* str = (char*)malloc(100); char* str2 = str; 浅拷贝只是对对象的简单拷贝,让几个对象共用一片内存,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针. iOS 里面的浅拷贝: 在 iOS 里面, 使用retain 关键字进行引用计数,就是一种更加保险的浅拷贝.他既让几个指针共用同一片内存空间,又可以在release 由于计数的存

Objective-C中的浅拷贝和深拷贝差异和区别

浅拷贝  浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间.如: char* str = (char*)malloc(100); char* str2 = str; 浅拷贝只是对对象的简单拷贝,让几个对象共用一片内存,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针. iOS 里面的浅拷贝:  在 iOS 里面, 使用retain 关键字进行引用计数,就是一种更加保险的浅拷贝.他既让几个指针共用同一片内存空间,又可以在release 由于计数