Java对象的深复制与浅复制

 

深复制与浅复制:

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


深复制(deep clone):

 

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

 

所有的基本(primitive)类型数据,无论是浅克隆还是深克隆,都会进行原值克隆。毕竟他们都不是对象,不是存储在堆中。注意:基本数据类型并不包括他们对应的包装类。

 

下面是一个实现深复制的例子:

创建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就互不影响了,但是由于String对象不能调用clone方法,所以String类型的属性并没有被克隆,所以这种克隆方法还是有问题的,想真正实现深克隆,必须使用序列化的方式:

 

Employer employer = new Employer();
employer.setUsername("Thinking in Java");


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


// employee2由employee深复制得到
Employee employee2 = (Employee) employee.clone();//
// 这样两个employee各自保存了两个employer
employee2.getEmployer().setUsername("Bruce Eckel");
System.out.println(employee.getEmployer().getUsername());
System.out.println(employee2.getEmployer().getUsername());
System.out.println(employee.getUsername());
System.out.println(employee2.getUsername());
System.out.println(employee.getUsername() == employee2.getUsername());
输出:
Thinking in Java
Bruce Eckel
Bruce Eckel
Bruce Eckel
true

 

使用序列化来进行对象的深复制:

序列化即是把对象写到流里面的过程;反序列化即是把对象从流中读取出来的过程。写在流里的是对象的一个拷贝,而原来的对象仍然在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("Thinking in Java");
		Employee2 employee = new Employee2();
		employee.setName("Bruce Eckel");
		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());
		System.out.println(employee.getName());
		System.out.println(employee2.getName());
		System.out.println(employee.getName() == employee2.getName());
	}

 

输出:

 

Thinking in Java
Jason
Bruce Eckel
Bruce Eckel
false

 

也可以写一个工具类来实现对象的深复制:

 

public class BeanUtil {

	@SuppressWarnings("unchecked")
	public static <T> T cloneTo(T src) throws RuntimeException {
		ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
		ObjectOutputStream out = null;
		ObjectInputStream in = null;
		T dist = null;
		try {
			out = new ObjectOutputStream(memoryBuffer);
			out.writeObject(src);
			out.flush();
			in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()));
			dist = (T) in.readObject();
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (out != null)
				try {
					out.close();
					out = null;
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			if (in != null)
				try {
					in.close();
					in = null;
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
		}
		return dist;
	}
}

 

测试:

 

Employer2 employer = new Employer2();
employer.setName("Thinking in Java");
Employee2 employee = new Employee2();
employee.setName("Bruce Eckel");
employee.setEmployer(employer);
// 通过深复制创建employee2
Employee2 employee2 = (Employee2) BeanUtil.cloneTo(employee);
employee2.getEmployer().setName("Bruce Eckel");


System.out.println(employee.getEmployer().getName());
System.out.println(employee2.getEmployer().getName());
System.out.println(employee.getName());
System.out.println(employee2.getName());
System.out.println(employee.getName() == employee2.getName());

 

输出:

 

Thinking in Java
Bruce Eckel
Bruce Eckel
Bruce Eckel
false

 

关于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接口。

参考:

 

Java基础笔记 – 对象的深复制与浅复制 实现Cloneable接口实现深复制 序列化实现深复制

Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨

 

 

posted @ 2016-09-05 15:54  john8169  阅读(140)  评论(0编辑  收藏  举报