详解Java中的clone


转帖请保留出处:http://blog.csdn.net/u011638883/article/details/11474073

       克隆就是创建一份原来对象的备份。字典中的意思是创建一份相同的备份。

        默认情况下,Java中的clone是成员变量的复制。但是因为在调用clone()方法并不知道类的成员变量的结构。所以当调用clone时java虚拟机(JVM)会执行以下动作:

        1)如果要克隆的类中只包含原始数据类型,那么会得到一个对象的完整克隆,并返回这个克隆对象的引用。

        2)如果要克隆的类中成员变量是其他的class类,那么这里只会复制这些成员变量的引用。

        java中的克隆实现:

        A) 使要克隆对象实现Cloneable接口

        B) 重写Object类中的clone()方法【很奇怪,为什么clone方法不是定义在cloneable接口中的】

        下面是Java docs中的clone方法:

/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object  [More ...] clone() throws CloneNotSupportedException;

         1) 首先声明保证克隆的对象存储在一个独立的内容地址。

         2) 其次声明原始对象和克隆对象应该是同一类型的,但这不是强制要求的。

         3) 最后申明原始对象和克隆对象调用equals()方法是应该返回true。但这不是强制要求的。

 下面我们来写一个demo试试,

先定义一个要克隆的类Employee

package com.wly.testclone;

public class Employee implements Cloneable {

	private Department department;
	private String employeeName;
	private int employeeId;
	
	public Employee(Department department, String employeeName, int employeeId) {
		super();
		this.department = department;
		this.employeeName = employeeName;
		this.employeeId = employeeId;
	}
	public Department getDepartment() {
		return department;
	}
	public void setDepartment(Department department) {
		this.department = department;
	}
	public String getEmployeeName() {
		return employeeName;
	}
	public void setEmployeeName(String employeeName) {
		this.employeeName = employeeName;
	}
	public int getEmployeeId() {
		return employeeId;
	}
	public void setEmployeeId(int employeeId) {
		this.employeeId = employeeId;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
}

可以看到Employee中包含了一个自定义的Department类,具体如下:

package com.wly.testclone;

public class Department {

	private int id;
	private String name;
	
	public Department(int id,String name) {
		this.id = id;
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

测试一下:

package com.wly.testclone;

public class TestCloning {

	public static void main(String[] args) 
			throws CloneNotSupportedException {
		Department department = new Department(1, "网络部");
		Employee original = new Employee(department, "盖茨-比", 101);
		Employee cloned = (Employee) original.clone();
		//验证克隆
		System.out.println(original != cloned); //输出:true
		System.out.println(original.getClass() == cloned.getClass());//输出:true
		System.out.println(original.equals(cloned));//输出:false
		
		Employee cloned2 = (Employee)original.clone();
		//修改克隆对象中类成员变量的值
		cloned2.getDepartment().setName("后勤部");
		//输出:后勤部,可见克隆包含"类"的成员变量时的类时,克隆的只是其成员变量的引用对象
		System.out.println(original.getDepartment().getName());
	}
}

程序运行结果以在注释中标明了,从中可以验证前面提到过了,包含非原始数据类型的成员变量(这里指的是Department)时,clone只是克隆了它的引用对象,那么怎么才能实现“真正”的克隆呢?这里需要了解一下深克隆和浅克隆。

浅复制(Shallow Cloning):是java中的默认实现。重写clone方法,假如你不需要克隆所有的object类型成员变量(不是原始类型),那么你可以使用浅克隆。

深克隆(Deep Cloning):是我们大多数希望实现的克隆方式。我们希望克隆一个独立于原目标对象的对象,从而是在其上的修改不会影响到原对象。

这里我们不妨推理一下怎么实现深克隆,因为在不包含object类型的成员变量的情况下,克隆是能够达到我们的要求的,那么我们只需要找到那个“不听话”的成员变量使其变得“听话”即可。这里我们可以是Department实现Cloneable接口,这样因为Department中不再包含object类型的成员变量,这样Department就实现了完整克隆,从而Employee也就是先了完整的克隆了。具体实现:

1)修改Department,使其实现Cloneable接口,并实现clone接口:

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

2)修改Employee中的clone方法:

	@Override
	protected Object clone() throws CloneNotSupportedException {
		Employee cloned = (Employee)super.clone();
		cloned.setDepartment((Department)cloned.getDepartment().clone());
		return cloned;
	}

再运行testCloning即可发现此时对克隆对象的修改已不会影响原对象了。

 

使用复制构造函数

复制构造函数是一个特殊的构造函数,它使用类的自身类型实例作为构造参数。如新建一个Student类,

package com.wly.testclone;

public class Student {

	private String name;
	private int age;

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	//复制构造函数
	public Student(Student s) {
		this.name = s.name;
		this.age = s.age;
	}
	
	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

测试一下,在testCloning中添加代码

Student s1 = new Student("奥特曼",23);
Student s2 = new Student(s1);
s1.setAge(34);
System.out.println(s2.getAge());
//输出:23

 

最后,可以使用序列化(Serializable)来实现对象的深克隆,当然使用序列化也有需要注意的地方,首先序列化动作是一个开销很大的操作,其次不是所有的对象都能序列化的,最后序列化是复杂的,可能带来一些意想不到的错误。

@SuppressWarnings("unchecked")
public static  T clone(T t) throws Exception {
	//检查T是否Serializeble是的实例,否则抛出CloneNotSupportedException
	ByteArrayOutputStream bos = new ByteArrayOutputStream();

	//序列化
	serializeToOutputStream(t, bos);
	byte[] bytes = bos.toByteArray();
	ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));

	//反序列化,并返回一个新的实例对象
	return (T)ois.readObject();
}

 

需要注意两点:

1)如果你不确定一个对象是否支持克隆,可以尝试这么写:

if(obj1 instanceof Cloneable) { obj2 = obj1.clone(); };

而不是直接obj2 = (Cloneable)obj1.clone();

2)当你用clone来创建对象时,是不会调用对象的构造方法的,如果你在构造方法里有初始化动作,或者你需要对象被调用的次数,那么在调用clone时你需要仔细一点。

 

注:翻译原文地址:http://howtodoinjava.com/2012/11/08/a-guide-to-object-cloning-in-java/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值