概述
Cloneable接口是一个空接口,仅用于标记对象,Cloneable接口里面是没有clone()方法,这个clone()方法是Object类里面的方法!默认实现是一个Native方法
如果对象implement Cloneable接口的话,需要覆盖clone方法(因为Object类的clone方法是protected,需要覆盖为public)
Object类里的clone()方法仅仅用于浅拷贝(拷贝基本成员属性,对于引用类型仅返回指向改地址的引用)
结合java虚拟机来看,克隆其实就是在堆中克隆出出一块和原对象一样的对象,并将这个对象的地址赋予新的引用
Object中的clone()方法
上面的注释省略,源码如下:
protected native Object clone() throws CloneNotSupportedException;
Object 对 clone() 方法的约定有三条:
- 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;
- 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的;
- 对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。
使用Cloneable接口对Java对象进行克隆
实现克隆的步骤:
- 让该类实现java.lang.Cloneable接口;
- 重写(override)Object类的clone()方法。
普通对象的克隆是克隆属性的值,而不是引用
public class CloneDemo implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneDemo cloneDemo = new CloneDemo();
cloneDemo.setName("aa");
System.out.println("cloneDemo---->" + cloneDemo.getName());
CloneDemo cloneDemo1 = cloneDemo;
cloneDemo1.setName("bb");
System.out.println("cloneDemo---->" + cloneDemo.getName());
System.out.println("cloneDemo1---->" + cloneDemo1.getName());
/******************克隆使用********************/
CloneDemo clone = new CloneDemo();
clone.setName("clone");
CloneDemo clone1 = (CloneDemo) clone.clone();
System.out.println("clone 和 clone1 是否相等--->" + (clone == clone1));
System.out.println("clone.equals(clone1)--->" + clone.equals(clone1));
clone1.setName("clonebb");
System.out.println("clone---->" + clone.getName());
System.out.println("clone1---->" + clone1.getName());
}
}
运行结果:
cloneDemo---->aa
cloneDemo---->bb
cloneDemo1---->bb
/******************克隆使用********************/
clone 和 clone1 是否相等--->false
clone.equals(clone1)--->false
clone---->clone
clone1---->clonebb
浅克隆和深克隆
浅克隆和深克隆:
浅克隆:(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。
简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示:
深克隆(Deep Clone):是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示:
1. 浅克隆,Object提供一个clone()方法,它就是浅克隆即很表层的克隆,拷贝规则如下:
- 基本类型
如果变量是基本类型,则拷贝其值,比如:int、float、long等。
- String字符串
这个比较特殊,克隆的是地址,是个引用,但是在修改的时候,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,此处可以认为String是个基本类型。
- 对象
如果变量是一个实例对象,则克隆地址引用(即克隆它自身以及它所包含的所有对象的引用地址),也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制,一个private修饰的变量,竟然可以被两个实例对象访问。
示例代码如下:
public class Person implements Cloneable {
private String name;
private Person father;
public Person(String name) {
this.name = name;
}
public Person(String name, Person father) {
this.name = name;
this.father = father;
}
/**
* 浅拷贝
* @return
*/
@Override
public Person clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
/**
* 1.重新覆写对象实例部分的拷贝,实现深拷贝
* 2.如果没有重写对象实例部分的拷贝,那么大儿子和小儿子的父亲会引用同一个父亲,
* 只要任意修改一下父亲,另外一个就会被修改
*/
p.setFather(new Person(p.getFather().getName()));
return p;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getFather() {
return father;
}
public void setFather(Person father) {
this.father = father;
}
public static void main(String[] args) throws CloneNotSupportedException {
//定义父亲
Person father = new Person("父亲");
//定义大儿子
Person son1 = new Person("大儿子",father);
//定义小儿子,通过克隆的方式
Person son2 = son1.clone();
//定义小儿子的name
son2.setName("小儿子");
//给小儿子,找个干爹
son2.getFather().setName("干爹");
System.out.println(son1.getName()+" 的父亲是 "+son1.getFather().getName());
System.out.println(son2.getName()+" 的父亲是 "+son2.getFather().getName());
}
}
没有去掉代码中的这一段(p.setFather(new Person(p.getFather().getName())))运行结果:
大儿子 的父亲是 父亲
小儿子 的父亲是 干爹
去掉后的运行结果:我们发现,仅仅是设置了小儿子的干爹,结果大儿子的父亲也变了
大儿子 的父亲是 干爹
小儿子 的父亲是 干爹
2. 深克隆,克隆除自身对象以外的所有对象,包括自身所包含的所有对象实例
如果想要实现对对象的深克隆,在不引入第三方jar包的情况下,可以使用两种办法:
- 先对对象进行序列化,紧接着马上反序列化出
- 先调用super.clone()方法克隆出一个新对象来,然后在子类的clone()方法中手动给克隆出来的非基本数据类型(引用类型)赋值,比如ArrayList的clone()方法:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
Arrays.copyOf()
如果是数组类型,我们可以直接使用 Arrays.copyOf() 来实现克隆,实现代码如下:
Person[] p1 = {new Person("starlin")};
Person[] p2 = Arrays.copyOf(p1,p1.length);
p1[0].setName("smartlin");
System.out.println("p1--->" + p1[0].getName());
System.out.println("p2--->" + p2[0].getName());
输出结果:
p1--->smartlin
p2--->smartlin
从结果可以看出,我们在修改克隆对象的第一个元素之后,原型对象的第一个元素也跟着被修改了,这说明 Arrays.copyOf() 其实是一个浅克隆。
因为数组比较特殊数组本身就是引用类型,因此在使用 Arrays.copyOf() 其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了。
深克隆实现方式汇总
深克隆的实现方式有很多种,大体可以分为以下几类:
- 所有对象都实现克隆方法;
- 通过构造方法实现深克隆;
- 使用 JDK 自带的字节流实现深克隆;
- 使用第三方工具实现深克隆,比如 Apache Commons Lang;
- 使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等
End,感谢阅读!!!