关于Clone
一般情况下,如果使用clone()
方法,则需满足以下条件。
1、对任何对象o,都有o.clone() != o
。换言之,克隆对象与原型对象不是同一个对象。
2、对任何对象o,都有o.clone().getClass() == o.getClass()
。换言之,克隆对象与原型对象的类型一样。
3、如果对象o的equals()方法定义恰当,则o.clone().equals(o)
应当成立。
我们在设计自定义类的clone()方法时,应当遵守这3个条件。一般来说,这3个条件中的前2个是必需的,第3个是可选的。
浅拷贝
super.clone()
方法直接从堆内存中以二进制流
的方式进行复制,重新分配一个内存块,因此其效率很高。
由于super.clone()
方法基于内存复制,因此不会调用对象的构造函数,也就是不需要经历初始化过程。
只有基本类型
的参数会被拷贝一份,非基本类型的对象不会被拷贝一份,而是继续使用传递引用
的方式,原型对象与克隆对象的该属性只是指向同一对象的引用,即浅拷贝。
@Override
public Object clone() throws CloneNotSupportedException{
Employees employees = (Employees)super.clone();
return employees;
}
深拷贝
在日常开发中,使用super.clone()
方法并不能满足所有需求。如类中存在引用对象属性。就要需要实现深拷贝,必须要自己手动修改 clone 方法才行。
@Override
public Object clone() throws CloneNotSupportedException{
List<String> temp = new ArrayList<String>();
for(String s : this.getEmpList()){
temp.add(s);
}
return new Employees(temp);
}
使用序列化(Serializable)
不过如果当原型对象维护很多引用属性的时候,手动分配会比较烦琐。因此,在Java中,如果想完成原型对象的深克隆,则通常使用 序列化(Serializable)的方式。
public class Employees implements Cloneable,Serializable{
private List<String> empList;
public Employees(){
empList = new ArrayList<String>();
}
public Employees(List<String> list){
this.empList=list;
}
public void loadData(){
//read all employees from database and put into the list
empList.add("Pankaj");
empList.add("Raj");
empList.add("David");
empList.add("Lisa");
}
public List<String> getEmpList() {
return empList;
}
@Override
public Object clone() throws CloneNotSupportedException{
List<String> temp = new ArrayList<String>();
for(String s : this.getEmpList()){
temp.add(s);
}
return new Employees(temp);
}
public Object deepClone() throws CloneNotSupportedException{
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Employees)ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
测试
public static void main(String[] args) throws CloneNotSupportedException {
Employees emps = new Employees();
emps.loadData();
Employees empsNew = (Employees) emps.deepClone();
Employees empsNew1 = (Employees) emps.deepClone();
List<String> list = empsNew.getEmpList();
list.add("John");
List<String> list1 = empsNew1.getEmpList();
list1.remove("Pankaj");
System.out.println("emps List: "+emps.getEmpList());
System.out.println("empsNew List: "+list);
System.out.println("empsNew1 List: "+list1);
}
emps List: [Pankaj, Raj, David, Lisa]
empsNew List: [Pankaj, Raj, David, Lisa, John]
empsNew1 List: [Raj, David, Lisa]
还原克隆破坏单例的事故现场
假设有这样一个场景,如果复制的目标对象恰好是单例对象,那会不会使单例对象被破坏呢?
当然,我们在已知的情况下肯定不会这么干,但如果发生了意外怎么办?不妨来试一下
public class A11_EagerInitializedSingletonClone implements Cloneable {
private static final A11_EagerInitializedSingletonClone instance = new A11_EagerInitializedSingletonClone();
// private constructor to avoid client applications to use constructor
private A11_EagerInitializedSingletonClone() {
}
public static A11_EagerInitializedSingletonClone getInstance() {
return instance;
}
@Override
public A11_EagerInitializedSingletonClone clone() throws CloneNotSupportedException{
A11_EagerInitializedSingletonClone employees = (A11_EagerInitializedSingletonClone)super.clone();
return employees;
}
}
A11_EagerInitializedSingletonClone instanceOne = A11_EagerInitializedSingletonClone.getInstance();
A11_EagerInitializedSingletonClone instanceOne2 = instanceOne.clone();
System.out.println(instanceOne==instanceOne2);
结果为false
确实创建了两个不同的对象。
实际上防止复制破坏单例对象的解决思路非常简单,禁止复制便可。要么我们的单例类不实现Cloneable
接口,要么我们重写clone()
方法,在clone()方法中返回单例对象即可,具体代码如下
@Override
public A11_EagerInitializedSingletonClone clone() throws CloneNotSupportedException{
return instance;
}
A11_EagerInitializedSingletonClone instanceOne = A11_EagerInitializedSingletonClone.getInstance();
A11_EagerInitializedSingletonClone instanceOne2 = instanceOne.clone();
System.out.println(instanceOne==instanceOne2);
结果为true