为什么对对象进行克隆
谈到了对象的克隆,就不得不说为什么要对对象进行克隆?Java中所有的对象都是保存在堆中,而堆是供全局共享的。也就是说,如果同一个Java程序的不同方法,只要能拿到某个对象的引用,引用者就可以随意的修改对象的内部数据(前提是这个对象的内部数据通过get/set方法曝露出来)。有的时候,我们编写的代码想让调用者只获得该对象的一个拷贝(也就是一个内容完全相同的对象,但是在内存中存在两个这样的对象),有什么办法可以做到呢?当然是克隆咯。
clone()函数和cloneable接口
先来看一下他们在文档中的描述:
Cloneable:
Clone():
通过上述的描述,我们可以看出,要让一个对象进行克隆,其实就是两个步骤:
- 让该类实现java.lang.Cloneable接口;
- 重写(override)Object类的clone()方法。
实例
class User implements Cloneable
{
int id;
int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(int id,int age){
this.id = id;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//一定要重写,否则利用equals()会出错
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
if (id != user.id) return false;
return true;
}
//一定要重写,否则利用equals()会出错.
@Override
public int hashCode() {
int result = id;
result = 31 * result + age;
return result;
}
}
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User(1,12);
User user2 = user1;
User user3 = (User) user1.clone();
System.out.println(user1==user2);//true
System.out.println(user1.equals(user2));//true
System.out.println(user1==user3);//false
System.out.println(user1.equals(user3));//true
}
}
注:刚开始的时候只是重写了clone(),致使System.out.println(user1.equals(user3));一直为false具体原因请参考浅谈java的==,equals(),hashcode()
但是事实真的就这样简单了,请看下面的代码:
class Administrator implements Cloneable
{
private User user;
private boolean editable;
public Administrator(User user,boolean editable){
this.user = user;
this.editable = editable;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public boolean isEditable() {
return editable;
}
public void setEditable(boolean editable) {
this.editable = editable;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Administrator that = (Administrator) o;
if (editable != that.editable) return false;
if (user != null ? !user.equals(that.user) : that.user != null) return false;
return true;
}
@Override
public int hashCode() {
int result = user != null ? user.hashCode() : 0;
result = 31 * result + (editable ? 1 : 0);
return result;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Administrator a1 = new Administrator(new User(1,12),true);
Administrator a2 = a1;
Administrator a3 = (Administrator) a1.clone();
System.out.println(a1==a2);//true
System.out.println(a1.equals(a2));//true
System.out.println(a1==a3);//false
System.out.println(a1.equals(a3));//false
System.out.println(a1.getUser()==a3.getUser());//true,不可思议了吧!!!
System.out.println(a1.getUser().equals(a3.getUser()));//true
}
}
出问题了吧!!!这里我们就可以引入两个专业的术语:浅克隆(shallow clone)和深克隆(deep clone)。
所谓的浅克隆,顾名思义就是很表面的很表层的克隆,如果我们要克隆Administrator对象,只克隆他自身以及他包含的所有对象的引用地址。
而深克隆,就是非浅克隆。克隆除自身以外所有的对象,包括自身所包含的所有对象实例。至于深克隆的层次,由具体的需求决定,也有“N层克隆”一说。
但是,所有的基本(primitive)类型数据,无论是浅克隆还是深克隆,都会进行原值克隆。毕竟他们都不是对象,不是存储在堆中。注意:基本数据类型并不包括他们对应的包装类。
如果我们想让对象进行深度克隆,我们可以这样修改Administrator类。
@Override
protected Object clone() throws CloneNotSupportedException {
Administrator a = (Administrator) super.clone();
//在方法内部调用持有对象的clone()方法;
a.user = (User) a.user.clone();
return a;
}
所以实现深度克隆的步骤如下:
- 让该类实现java.lang.Cloneable接口;
- 确认持有的对象是否实现java.lang.Cloneable接口并提供clone()方法;
- 重写(override)Object类的clone()方法,并且在方法内部调用持有对象的clone()方法;
- ……
- 多麻烦啊,调来调去的,如果有N多个持有的对象,那就要写N多的方法,突然改变了类的结构,还要重新修改clone()方法。
难道就没有更好的办法吗?
此时,上一篇浅谈java的标识接口-Serializable就派上用场了!
首先编写一个工具类:
public abstract class BeanUtil {
@SuppressWarnings("unchecked")
public static Administrator cloneTo(Administrator src){
ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
ObjectOutputStream out = null;
ObjectInputStream in = null;
Administrator dist = null;
try {
out = new ObjectOutputStream(memoryBuffer);
out.writeObject(src);
out.flush();
in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()));
dist = (Administrator) 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;
}
}
使User,Administrator实现接口Serializable
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Administrator a1 = new Administrator(new User(1,12),true);
Administrator a3 = BeanUtil.cloneTo(a1);
System.out.println(a1==a3);//false
System.out.println(a1.equals(a3));//true
System.out.println(a1.getUser()==a3.getUser());//false
System.out.println(a1.getUser().equals(a3.getUser()));//true
}
}
好了,无论你的对象有多么的复杂,只要这些对象都能够实现java.lang.Serializable接口,就可以进行克隆,而且这种克隆的机制是JVM完成的,不需要修改实体类的代码,方便多了。
为什么这么简单就可以实现对象的克隆呢?java.lang.Serializable接口又是干嘛用的呢?
**把对象写到流里的过程是序列化过程(Serialization),而把对象从流中读出来的过程则叫做反序列化过程(Deserialization)。(上面的BeanUtil就是实现序列化反序列化的工具类.)
应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将其排除在复制过程之外。**