为何要克隆html
为何要使用克隆,这其实反映的是一个很现实的问题,假如咱们有一个对象:app
public class SimpleObject implements Cloneable { private String str; public SimpleObject() { System.out.println("Enter SimpleObject.constructor()"); } public String getStr() { return str; } public void setStr(String str) { this.str = str; } public Object clone() throws CloneNotSupportedException { return super.clone(); } }
如今我写一段程序:函数
public static void main(String[] args) { SimpleObject so0 = new SimpleObject(); so0.setStr("111"); System.out.println("so0.getStr():" + so0.getStr()); SimpleObject so1 = so0; so1.setStr("222"); System.out.println("so0.getStr():" + so0.getStr()); System.out.println("so1.getStr():" + so1.getStr()); }
运行结果其实很明显:this
so0.getStr():111 so0.getStr():222 so1.getStr():222
Java底层使用C/C++实现的,"="这个运算符,若是左右两边都是对象引用的话,在Java中表示的将等号右边的引用赋值给等号左边的引用,两者指向的仍是同一块内存,因此任何一个引用对内存的操做都直接反映到另外一个引用上。spa
可是,如今我想拿这个so0的数据进行一些操做,不想改变原来so0中的内容,这时候就可使用克隆了,它容许在堆中克隆出一块和原对象同样的对象,并将这个对象的地址赋予新的引用,这样,显然我对新引用的操做,不会影响到原对象。code
固然,理解克隆,最好仍是对Java内存区域有比较好的理解。htm
Cloneable接口和Object的clone()方法对象
Java中实现了Cloneable接口的类有不少,像咱们熟悉的ArrayList、Calendar、Date、HashMap、Hashtable、HashSet、LinkedList等等。blog
仍是那句话,对于不熟悉的接口、方法,第一反应必定是查询JDK API。接口
一、Cloneable接口
三句话总结:
(1)此类实现了Cloneable接口,以指示Object的clone()方法能够合法地对该类实例进行按字段复制
(2)若是在没有实现Cloneable接口的实例上调用Object的clone()方法,则会致使抛出CloneNotSupporteddException
(3)按照惯例,实现此接口的类应该使用公共方法重写Object的clone()方法,Object的clone()方法是一个受保护的方法
二、Object的clone()方法
建立并返回此对象的一个副本。对于任何对象x,表达式:
(1)x.clone() != x为true
(2)x.clone().getClass() == x.getClass()为true
(3)x.clone().equals(x)通常状况下为true,但这并非必需要知足的要求
克隆实例
把上面例子的main函数修改一下:
public static void main(String[] args) throws Exception { SimpleObject so0 = new SimpleObject(); so0.setStr("111"); SimpleObject so1 = (SimpleObject)so0.clone(); System.out.println("so0 == so1?" + (so0 == so1)); System.out.println("so0.getClass() == so1.getClass()?" + (so0.getClass() == so1.getClass())); System.out.println("so0.equals(so1)?" + (so0.equals(so1))); so1.setStr("222"); System.out.println("so0.getStr():" + so0.getStr()); System.out.println("so1.getStr():" + so1.getStr()); }
看一下运行结果:
Enter SimpleObject.constructor() so0 == so1?false so0.getClass() == so1.getClass()?true so0.equals(so1)?false so0.getStr():111 so1.getStr():222
获得三个结论:
一、克隆一个对象并不会调用对象的构造方法,由于"Enter SimpleObject.constructor()"语句只出现了一次
二、符合JDK API的clone()方法三条规则
三、so1对于SimpleObject对象str字段的修改不再会影响到so0了
浅克隆和深克隆
浅克隆(shallow clone)和深克隆(deep clone)反映的是,当对象中还有对象的时候,那么:
一、浅克隆,即很表层的克隆,若是咱们要克隆对象,只克隆它自身以及它所包含的全部对象的引用地址
二、深克隆,克隆除自身对象之外的全部对象,包括自身所包含的全部对象实例
这两个概念应该很好理解,就不写代码了。多提一句,全部的基本数据类型,不管是浅克隆仍是深克隆,都会进行原值克隆,毕竟它们都不是对象,不是存储在堆中的。
那其实Object的clone()方法,提供的是一种浅克隆的机制,若是想要实现对对象的深克隆,在不引入第三方jar包的状况下,可使用两种办法:
一、先对对象进行序列化,紧接着立刻反序列化出
二、先调用super.clone()方法克隆出一个新对象来,而后在子类的clone()方法中手动给克隆出来的非基本数据类型(引用类型)赋值,好比ArrayList的clone()方法:
public Object clone() { try { ArrayList<E> v = (ArrayList<E>) 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(); } }