1.为什么需要克隆?
在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的,要满足这种需求有很多途径。看例子:
public class Test implements Cloneable { public String str; public Test(){
System.out.println("Test的构造方法");
}
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) throws CloneNotSupportedException{ Test t1 = new Test(); t1.setStr("1"); System.out.println(t1.getStr()); Test t2 = new Test(); t2=t1; t1.setStr("222"); System.out.println(t1.getStr()); System.out.println(t2.getStr()); } }
运行结果:
1 222 222
Java底层使用C/C++实现的,"="这个运算符,如果左右两边都是对象引用的话,在Java中表示的将等号右边的引用赋值给等号左边的引用,二者指向的还是同一块内存,所以任何一个引用对内存的操作都直接反映到另一个引用上。
但是,现在我想拿这个so0的数据进行一些操作,不想改变原来so0中的内容,这时候就可以使用克隆了,它允许在堆中克隆出一块和原对象一样的对象,并将这个对象的地址赋予新的引用,这样,显然我对新引用的操作,不会影响到原对象。
2.浅克隆
浅度克隆对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于非基本数据类型的属性,仅仅复制一份引用给新产生的对象,即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。
浅克隆的步骤:
1.实现java.lang.Cloneable接口
(1):实现Cloneable接口,不包含任何方法!仅仅是用来指示Object类中clone()方法可以用来合法的进行克隆。
(2):如果在没有实现Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupporteddException
(3): 实现此接口的类,应该使用public,重写Object的clone方法。Object类中的clone()是一个protected属性的方法,重写之后要把clone()方法的属性设置为public。因为在Object类中的clone()方法是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。
2.重写java.lang.Object.clone()方法
创建并返回此对象的一个副本。对于任何对象x,表达式:
(1)x.clone() != x为true
(2)x.clone().getClass() == x.getClass()为true
(3)x.clone().equals(x)一般情况下为true,但这并不是必须要满足的要求
Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
实例:将上面例子改写一下:
public static void main(String[] args) throws CloneNotSupportedException{ Test t1 = new Test(); t1.setStr("1"); Test t2 = (Test)t1.clone(); System.out.println("so0 == so1?" + (t1 == t2)); System.out.println("so0.getClass() == so1.getClass()?" + (t1.getClass() == t2.getClass())); System.out.println("so0.equals(so1)?" + (t1.equals(t2))); t1.setStr("222"); System.out.println("so0.getStr():" + t1.getStr()); System.out.println("so1.getStr():" + t2.getStr()); }
输出结果:
TEST的构造方法 so0 == so1?false so0.getClass() == so1.getClass()?true so0.equals(so1)?false so0.getStr():222 so1.getStr():1
得到三个结论:
1、克隆一个对象并不会调用对象的构造方法,因为"Test的构造方法"语句只出现了一次
2、符合JDK API的clone()方法三条规则
3、t2对Test对象str字段的修改再也不会影响到t1了
3.深克隆
深克隆简单的说就是:除了克隆自身对象,还对其他非基本数据类型的引用的其他以外的所有对象,都克隆了一遍。
深克隆的步骤:
1.首先克隆的类,也必须实现Cloneable接口和重写Object的clone()的方法。
2.在不引入第三方jar包的情况下,可以使用两种办法:
(1)、先对对象进行序列化,紧接着马上反序列化出。
(2)、先调用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(); } }
4.第三方jar包
对于克隆,java还提供了一些好用的第三方jar包,比如:
(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)都是相当于克隆中的浅克隆。