Java的对象都是引用,当将一个对象赋值给另外一个对象的时候, 也就是说指针(当然,java没有指针的概念)同指向同一块内存地址。
所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。
我们知道,Java是纯面向对象的程序设计语言。
Java里,所有的类的顶级父类都是java.lang.Object类,
也就是说,如果一个类没有显示 申明继承关系,它的父类默认就是java.lang.Object。
有一个很简单的方法可以证明这一点,我们写一个Test类,如下:
public class Test {
public void someMethod() {
super.clone();
}
}
里面调用了super.clone(),编译时并不报错。其实clone()方法为java.lang.Object类提供的一个 protected型方法。
对象克隆
本文通过介绍java.lang.Object#clone()方法来说明Java语言的对象克隆特性。
java.lang.Object#clone()方法由java.lang.Object加以实现,主要对对象本身加以克隆。
首先我们看看下面的例子:
public class TestClone { public static void main(String[] args) { MyClone myClone1 = new MyClone("clone1"); MyClone myClone2 = (MyClone)myClone1.clone(); if (myClone2 != null) { System.out.println(myClone2.getName()); System.out.println("myClone2 equals myClone1: " + myClone2.equals(myClone1)); } else { System.out.println("Clone Not Supported"); } } } class MyClone { private String name; public MyClone(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } }
一方面将clone()访问级别设置为protected型,以限制外部类访问;
另一方面,强制需要提供clone功能的子类实现java.lang.Cloneable接口,
在运行期,JVM会检查调用clone()方法的 类,如果该类未实现java.lang.Cloneable接口,则抛出CloneNotSupportedException异常。
java.lang.Cloneable接口是一个空的接口,没有申明任何属性与方法。该接口只是告诉JVM,该接口的实现类需要开放“克隆”功 能。
我们再将MyClone类稍作改变,让其实现Cloneable接口:
class MyClone implements Cloneable {
...//其余不做改变
}
编译执行TestClone,打印出:
C:\clone>javac *.java
C:\clone>java TestClone
clone1
myClone2 equals myClone1 : false
C:\clone>
根据结果,我们可以发现:
1,myClone1.clone()克隆了跟myClone1具有相同属性值的对象
2,但克隆出的对象myClone2跟myClone1不是同一个对象(具有不同的内存空间)
小结:
如果要让一个类A提供克隆功能,该类必须实现java.lang.Cloneable接口,并重载 java.lang.Object#clone()方法。
public class A extends Cloneable { public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { //throw (new InternalError(e.getMessage())); return null; } } }对象的深层次克隆
验证对象的克隆方式
为了验证对象的克隆方式,我们对上面的例子加以改进,如下
import java.util.ArrayList; import java.util.List; public class TestClone { public static void main(String[] args) { // 为克隆对象设置值 MyClone myClone1 = new MyClone("clone1"); myClone1.setBoolValue(true); myClone1.setIntValue(100); // 设置List值 List<Element> listValue = new ArrayList<Element>(); listValue.add(new Element("ListElement1")); listValue.add(new Element("ListElement2")); listValue.add(new Element("ListElement3")); myClone1.setListValue(listValue); // 设置Element值 Element element1 = new Element("element1"); myClone1.setElement(element1); // 克隆 MyClone myClone2 = (MyClone) myClone1.clone(); if (myClone2 != null) { // 简单属性 System.out.println("myClone2.name=" + myClone2.getName() + " myClone2.boolValue=" + myClone2.isBoolValue() + " myClone2.intValue=" + myClone2.getIntValue()); // 复合属性(List<Element>与Element) List clonedList = myClone2.getListValue(); Element element2 = myClone2.getElement(); System.out .println("myClone2.listValue.size():" + clonedList.size()); System.out.println("myClone2.element.equals(myClone1.element):" + element2.equals(element1)); // 打印结果 : element1. System.out.println("myClone2.element.name:" + element2.getName()); /* * * 下面我们测试一下myClone2.element是否等于myClone1.element * 以及myClone2.listValue是否等于myClone1.listValue * * 为此,我们修改myClone2.element与myClone2.listValue, * 如果myClone1的相应值也跟着被修改了,则它们引用 的是同一个内存空间的变量,我们认为它们相等 . */ clonedList.add("ListElement4"); // 打印结果 : 4. System.out.println("myClone1.listValue.size():" + listValue.size()); element2.setName("Element2"); // 打印结果为: Element2. System.out.println("myClone1.element.name:" + element1.getName()); } else { System.out.println("Clone Not Supported"); } } } class MyClone implements Cloneable { private int intValue; private boolean boolValue; private String name; private List<Element> listValue; private Element element; public MyClone(String name) { this.name = name; } public int getIntValue() { return intValue; } public void setIntValue(int intValue) { this.intValue = intValue; } public boolean isBoolValue() { return boolValue; } public void setBoolValue(boolean boolValue) { this.boolValue = boolValue; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Element> getListValue() { return listValue; } public void setListValue(List<Element> listValue) { this.listValue = listValue; } public Element getElement() { return element; } public void setElement(Element element) { this.element = element; } /***** * * 手动在重载的clone()方法里,对属性也分别采用克隆操作。 * * 当然条件是,属性类也得支持克隆操作 * */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { //throw (new InternalError(e.getMessage())); return null; } } } class Element implements Cloneable { private String name; public Element(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } /***** 手动在重载的clone()方法里,对属性也分别采用克隆操作。 当然条件是,属性类也得支持克隆操作 */ public Element clone() { try { return (Element) super.clone(); } catch (CloneNotSupportedException e) { return null; } } }
myClone2.name=clone1 myClone2.boolValue=true myClone2.intValue=100
myClone2.listValue.size():3
myClone2.element.equals(myClone1.element):true
myClone2.element.name:element1
myClone1.listValue.size():4
myClone1.element.name:Element2
我们发现,对于对象里的List,Element等复合属性,super.clone()只是简单地赋值,没有采取克隆手段。
也就是说,修改被克 隆后的对象值,会影响到原对象。
怎么进行深层次的克隆呢?
答案是,我们只能手动在重载的clone()方法里,对属性也分别采用克隆操作。当然条件是,属性类也得支持克隆操作
class MyClone implements Cloneable {
...
public Object clone() { try { MyClone myClone = (MyClone)super.clone(); //分别对属性加以克隆操作 myClone.element = this.element.clone(); myClone.listValue = new ArrayList(); for (Element ele:this.listValue) { myClone.listValue.add(ele.clone()); } return myClone; } catch (CloneNotSupportedException e) { return null; } }
...
}
//让Element类也支持克隆操作
class Element implements Cloneable {
...
public Element clone() {
try {
return (Element)super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
一个方法自然是重写clone方法,添加如order.items=(LineItems)items.clone()的语句,也就是人为地添加对引用对象的复制。这个方法的缺点是如果引用对象有很多,或者说引用套引用很多重,那么太麻烦了。业界常用的方法是使用串行化然后反串行化的方法来实现深克隆。由于串行化后,对象写到流中,所有引用的对象都包含进来了,所以反串行化后,对等于生成了一个完全克隆的对象。绝!
可借鉴:http://blog.csdn.net/accp_fangjian/article/details/2423252
这个方法的要求是对象(包括被引用对象)必须事先了Serializable接口,否则就要用transient关键字将其排除在复制过程中。
再举一个例子:
package com.itm.clone; class CloneStudent implements Cloneable { String name; int age; CloneStudent(String name, int age) { this.name = name; this.age = age; } public Object clone() { Object o = null; try { // Object 中的clone()识别出你要复制的是哪一个对象。 o = (CloneStudent) super.clone(); } catch (CloneNotSupportedException e) { System.out.println(e.toString()); } return o; } public static void main(String[] args) { CloneStudent s1 = new CloneStudent("zhangsan", 18); CloneStudent s2 = (CloneStudent) s1.clone(); s2.name = "lisi"; s2.age = 20; // 修改学生2后,不影响学生1的值。 System.out.println("name=" + s1.name + "," + "age=" + s1.age); } }
package com.itm.clone; // 若 Professor 不实现 Clonebale以及 重写 clone()方法,则是 浅克隆。。。 // // 学生1的教授成为lisi,age为30。 class Professor implements Cloneable{ String name; int age; Professor(String name, int age) { this.name = name; this.age = age; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { //throw (new InternalError(e.getMessage())); return null; } } } class Student implements Cloneable { String name;// 常量对象。 int age; Professor p;// 学生1和学生2的引用值都是一样的。 Student(String name, int age, Professor p) { this.name = name; this.age = age; this.p = p; } public Object clone() { Student o = null; try { o = (Student) super.clone(); } catch (CloneNotSupportedException e) { System.out.println(e.toString()); } // 去掉下面一行:学生1的教授成为lisi,age为30。 o.p = (Professor) p.clone(); return o; } } public class TwoTestClone{ public static void main(String[] args) { Professor p = new Professor("wangwu", 50); Student s1 = new Student("zhangsan", 18, p); Student s2 = (Student) s1.clone(); s2.p.name = "lisi"; s2.p.age = 30; // 学生1的教授 name=wangwu,age=50 System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age); } }