对象拷贝

2018-05-22  本文已影响10人  8813d76fee36

浅拷贝

什么是浅拷贝

浅拷贝会将原对象中的基本类型的变量的值和引用类型变量的值复制到新的对象中。
浅复制得到的新对象内部的引用对象与原对象的指向同一个引用的对象。

如何实现浅拷贝

protected native Object clone() throws CloneNotSupportedException;

该方法是一个本地(native)方法,对于任何对象 x ,clone()操作有如下特点:

  1. 表达式 x.clone() != x结果应为true 。 说明拷贝后得到的是一个新的对象,而不是对原对象的引用。
  2. 表达式 x.clone().getClass() == x.getClass() 结果应该为true。说明拷贝得到的对象与原对象是同一类型,但这并不是必须的。
  3. 表达式 x.clone().equals(x) 结果应该为true,但这不是必须的。

浅拷贝实现及现象

实现对Person类的实例的浅拷贝。

  1. 实现 Cloneable接口
    该接口是一个标记接口(类似Serializable),标识该类的实例可以执行clone()操作,若不实现该接口,执行clone()操作会抛出CloneNotSupportedException异常。
public class Person implements Cloneable {

    private int age; // 年龄
    private String name; // 姓名
    private Address address; // 地址信息

    public Person(int age, String name, Address address) {
        this.age = age;
        this.name = name;
        this.address = address;
    }
    // getter / setter

    @Override
    public String toString() {
        return "name: " + name + " -- age: " + age + " -- home: " + address.getHome();
    }

    @Override
    public Person clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }
}
  1. 重写Object类的clone()方法
@Override
    public Person clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }

在重写clone()方法时,主要做了这些改动:

  1. 将方法声明为public(Object类中是protected
  2. 调用super.clone();
    在运行时刻,Object中的 clone() 识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
  3. 将拷贝后的对象转为目标类型,并返回。
public class Address {
    private String home; // 住址
    // getter / setter
}
public class Run {
    public static void main(String[] args) {

        Address address = new Address("beijing");
        Person person = new Person(18, "wj", address);

        Person person2 = person.clone();

        System.out.println("Person1: " + person);
        System.out.println("Person2: " + person2);

        System.out.println();
        System.out.println("---------------");
        System.out.println();

        person.setAge(30);
        address.setHome("shanghai");

        System.out.println("Person1: " + person);
        System.out.println("Person2: " + person2);
    }
}
Person1: name: wj -- age: 18 -- home: beijing
Person2: name: wj -- age: 18 -- home: beijing

---------------

Person1: name: wj -- age: 30 -- home: shanghai
Person2: name: wj -- age: 18 -- home: shanghai

此时可以发现:

  1. person1执行clone()方法后得到person2对象,两个对象中的内容一模一样。
  2. 当修改person1的年龄(基本类型 int)后,person2并没有受到影响。
  3. 当修改person1引用的address对象(引用类型)的值后,person2的值也跟着改变。这也说明浅复制中新对象与原对象内部引用的其他对象指向的是同一个对象。

如果想在修改person1的address的值而不影响person2,则需要使用深拷贝。

深拷贝

与浅拷贝不同,深拷贝会将对象中引用的其他对象也复制一份新的实例出来分配给拷贝得到的新对象,而不仅仅是复制地址引用。
这也是深拷贝与浅拷贝最根本的不同。

如何实现深拷贝

  1. 同时拷贝引用的对象
    我们需要在拷贝原对象的同时将其引用的其他对象也进行拷贝。如上面的例子,Person中引用了Address,那么Address也需要实现Cloneable接口,并重写clone()方法,在Person拷贝的同时执行拷贝操作。
public class Address implements Cloneable {
    private String home; // 住址

    // getter / setter

    @Override
    public Address clone() {
        Address address = null;
        try {
            address = (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return address;
    }
}
@Override
    public Person clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
            if (address != null) {
                person.address = address.clone();
            }
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }
Person1: name: wj -- age: 18 -- home: beijing
Person2: name: wj -- age: 18 -- home: beijing

---------------

Person1: name: wj -- age: 30 -- home: shanghai
Person2: name: wj -- age: 18 -- home: beijing
  1. 使用序列化实现
    方法1解决了眼前的问题,如果Address中也包括其他对象的引用,其他对象又包括其他对象的引用,那么为了实现深拷贝,我们需要不断的逐层重复方法1的操作(多层克隆问题)。
    为了解决多层克隆问题,我们可以使用序列化的方式来实现深层克隆。
public Person personClone() {
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bais = null;
        ObjectInputStream ois = null;
        
        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);

            oos.writeObject(this);

            bais = new ByteArrayInputStream(baos.toByteArray());
            ois = new ObjectInputStream(bais);

            return (Person) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            release(baos);
            release(oos);
            release(bais);
            release(ois);
        }
        return null;
    }

    private void release(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Person person2 = person.personClone();
Person1: name: wj -- age: 18 -- home: beijing
Person2: name: wj -- age: 18 -- home: beijing

---------------

Person1: name: wj -- age: 30 -- home: shanghai
Person2: name: wj -- age: 18 -- home: beijing

参考文章:
Java中对象的深复制和浅复制详解
Java提高篇——对象克隆(复制)

上一篇下一篇

猜你喜欢

热点阅读