1、前言
因为最近在了解设计模式,其中创建型模式中有一种原型模式,该模式采用克隆的方式,克隆原型实例,来生成一个克隆对象,提供给调用者。关于克隆,以前在看Object类源码时,看到有一个clone方法,但是没太在意,所以也不太了解,看来需要有一些了解了。
2、克隆
什么是克隆?可以简单的理解为复制,我们在电脑上进行文件复制的时候,实际上就是属于克隆。但是有时候克隆和复制并不是完全等同的。Java中的克隆主要指对象的克隆,即我new出来一个对象,然后通过clone方法对这个对象进行克隆,那么将会产生一个与这个原型实例等同的克隆实例。
先看api文档:
通过api文档,获取到了几个主要的信息:克隆需要覆写Object类中clone方法,然后调用super.clone来返回一个克隆对象。产生对象的类需要实现java.lang.Cloneable接口,否则会抛出异常。最后就是关于浅克隆和深克隆的问题。
先进行一个简单的测试。创建一个实现Cloneable接口的实体类,如下:
/*
* 如果需要克隆该类的实例,那么这个类必须要实现Cloneable接口
*/
public class User implements Cloneable {
private String name;
private String sex;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
// 覆写Object类中的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
// 克隆对象的话,可以直接使用super.clone()来获得
return super.clone();
}
// 覆写equals方法,规定相等原则
@Override
public boolean equals(Object obj) {
User u = (User)obj;
if(this.getName().equals(u.getName()) &&
this.getSex().equals(u.getSex()) &&
this.getAge() == u.getAge()) {
return true;
}
return false;
}
}
java.lang.Cloneable接口的源码:
接口中并没提供太多有用的内容,它链接到了Object类中的clone方法。
java.lang.Object类的部分源码:
值得注意的是,clone方法是一个native本地方法,那就不去研究那么多了,直接用就行,这个方法会抛出CloneNotSupportedException的异常,当且仅当需要产生克隆对象的类没有实现Cloneable接口时才会抛出这个异常。
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
User u = new User();
u.setName("ycz");
u.setSex("男");
u.setAge(25);
// 这种是引用地址的赋值,相当于栈中的u和u2都指向堆中的同一实例
User u2 = u;
System.out.println(u == u2);
System.out.println(u.equals(u2));
System.out.println(u.getClass() == u2.getClass());
System.out.println("----------------");
// 使用clone方法克隆对象
User u3 = (User) u.clone();
System.out.println(u == u3);
System.out.println(u.equals(u3));
System.out.println(u.getClass() == u3.getClass());
System.out.println("姓名:" + u3.getName());
System.out.println("性别:" + u3.getSex());
System.out.println("年龄:" + u3.getAge());
}
}
可以看到,克隆出来的对象和原型实例,引用地址是不同的,由于重写了equals方法,所以此方法的比较返回了结果true。不管是原型对象,还是克隆实例,它们的运行时类是一样的,这一点无论怎样都不会改变。
3、浅克隆
api文档中提到了浅克隆和深克隆这两个概念,先看浅克隆。
浅克隆仅仅只克隆对象本身,包括一些基本类型数据,但是它不会克隆对象包含的引用所指向堆中的对象。
什么意思呢,这话听起来有一点绕,通俗的讲,就是基本类型数据可以克隆,但是引用的对象不会克隆,就是说克隆对象和原型实例的引用所指向的是堆中的同一对象。
创建一个Dog类:
public class Dog implements Cloneable {
private String name;
private String bodyColor;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBodyColor() {
return bodyColor;
}
public void setBodyColor(String bodyColor) {
this.bodyColor = bodyColor;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
修改User类:
只是添加了一个引用类型的成员属性和它的get/set方法。然后测试:
public static void main(String[] args) throws CloneNotSupportedException {
User u = new User();
u.setName("ycz");
u.setSex("男");
u.setAge(25);
Dog dog = new Dog();
dog.setName("李狗蛋");
dog.setBodyColor("白色");
u.setDog(dog);
// 浅克隆
User u2 = (User) u.clone();
u2.setName("云过梦无痕");
Dog d = u2.getDog();
d.setName("小钢蛋儿");
d.setBodyColor("黑白相间");
System.out.println("原型实例如下:");
System.out.println("姓名:" + u.getName());
System.out.println("性别:" + u.getSex());
System.out.println("宠物狗狗的名字:" + u.getDog().getName());
System.out.println("宠物狗狗的毛色:" + u.getDog().getBodyColor());
System.out.println("-------------------------");
System.out.println("克隆实例如下:");
System.out.println("姓名:" + u2.getName());
System.out.println("性别:" + u2.getSex());
System.out.println("宠物狗狗的名字:" + u2.getDog().getName());
System.out.println("宠物狗狗的毛色:" + u2.getDog().getBodyColor());
}
可以看到,上面只改了克隆对象中引用对象的属性值,但是原型实例中也一并改变了,说明浅克隆出来的对象中所包含的引用和原型实例中的引用指向的是同一个对象,也就是浅克隆只能克隆基本数据类型,无法克隆引用类型。
3、深克隆
深克隆和浅克隆的区别在于深克隆可以克隆引用类型。
修改User类中的clone方法,进行两次克隆,如下:
测试代码不用改,再测试一次:
可以看到,修改克隆对象的引用对象属性值,原型对象的引用对象属性值不会发生改变,间接的说明了克隆对象中的引用和原型对象中的引用指向的不是堆中同一个对象,也说明了深层次的克隆是完全克隆原型实例,包括基本数据和引用类型,这也是和浅克隆的区别,注意即可。