4.1.10 Java中的clone方法有什么作用
虽然说Java语言取消了指针的概念,但是这只是在Java语言中没有明确提供指针的概念与用法,实质上每个new语句返回的都是一个指针引用,只不过大部分情况下开发人员不需要关心如何去操作这个指针而已。
举例如下:
class Obj{
public void setStr(String str){
this.str = str;
}
private String str = "default value";
public String toString(){
return str;
}
}
public class TestRef {
private Obj aObj = new Obj();
private int aInt = 0;
public Obj getAObj(){
return aObj;
}
public int getAInt(){
return aInt;
}
public void changeObj(Obj inObj){
inObj.setStr("changed value");
}
public void changeInt(int inInt){
inInt = 1;
}
public static void main(String[] args){
TestRef oRef = new TestRef();
System.out.println("******************引用类型*******************");
System.out.println("调用changeObj()前:"+oRef.getAObj());
oRef.changeObj(oRef.getAObj());
System.out.println("调用changeObj()后:"+oRef.getAObj());
System.out.println("******************基本数据类型****************");
System.out.println("调用changeInt()前:"+oRef.getAInt());
oRef.changeInt(oRef.getAInt());
System.out.println("调用changeInt()后:"+oRef.getAInt());
}
}
运行结果如下:
******引用类型*********
调用changeObj()前:default value
调用changeObj()后:changed value
******基本数据类型*****
调用changeInt()前:0
调用changeInt()后:0
上面对两个变量近乎相同的操作,却得到了不同的结果,主要原因是Java在处理基本数据类型(如int、char、double等)时,都是采用按值传递(传递的是输入参数的复制)的方式执行,除此之外的其他类型都是按照引用传递(传递的是对象的一个指针引用)的方式执行。对象除了在函数调用时是引用传递,在使用“=”赋值时也采用引用传递。示例如下:
class Obj{
private int aInt = 0;
public int getAInt(){
return aInt;
}
public void setAInt(int int1){
aInt = int1;
}
public void changeInt(){
this.aInt = 1;
}
}
public class TestRef {
public static void main(String[] args){
Obj a = new Obj();
System.out.println("调用changeInt()之前的a:"+a.getAInt());
Obj b = a;
b.changeInt();
System.out.println("调用changeInt()之后的a:"+a.getAInt());
System.out.println("b:"+b.getAInt());
}
}
运行结果:
调用changeInt()之前的a:0
调用changeInt()之后的a:1
b:1
从上例中可以看出,仅仅通过简单的赋值操作是无法实现,从某个已有的对象A创建出另一个与A具有相同状态的对象B,而且对B修改不会影响到A的情况。Java正好提供了一个简单有效的clone()方法来满足这个需求。
Java中的所有类默认都继承自Object类,而Object类中提供了一个clone()方法,这个方法作用是返回一个Object对象的复制,也就是说返回的是一个新的对象引用而不是原对象的引用。 以下是使用clone()方法的步骤: 1>需要实现clone()方法的类首先需要继承Cloneable接口。Cloneable接口实质上回一个标识接口,没有任何接口方法。
2>在类中重写Object类中的clone()方法。
3>在clone方法中调用super.clone()。无论clone类的继承结构是什么,super.clone()都会直接或简介调用java.lang.Object类的方法clone()方法
4>把浅复制的引用指向原型对象新的克隆体。
对上例中的代码使用浅复制:
class Obj implements Cloneable{
private int aInt = 0;
public int getAInt() {
return aInt;
}
public void setAInt(int aInt) {
this.aInt = aInt;
}
public void changeInt(){
this.aInt = 1;
}
public Object clone(){
Object o = null;
try{
o = (Obj)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
public class TestRef {
public static void main(String[] args){
Obj a = new Obj();
System.out.println("调用changeInt()之前的a:"+a.getAInt());
Obj b = (Obj)a.clone();
b.changeInt();
System.out.println("调用changeInt()之后的a:"+a.getAInt());
System.out.println("b:"+b.getAInt());
}
}
运行结果如下:
调用changeInt()之前的a:0
调用changeInt()之后的a:0
b:1
当类包含对象成员时,浅复制又是怎样的呢?
实例如下:
class Professor0 implements Cloneable {
String name;
int age;
Professor0(String name, int age) {
this.name = name;
this.age = age;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Student0 implements Cloneable {
String name;// 常量对象。
int age;
Professor0 p;// 学生1和学生2的引用值都是一样的。
Student0(String name, int age, Professor0 p) {
this.name = name;
this.age = age;
this.p = p;
}
public Object clone() {
Student0 o = null;
try {
o = (Student0) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
}
public class ShallowCopy {
public static void main(String[] args) {
Professor0 p = new Professor0("wangwu", 50);
Student0 s1 = new Student0("zhangsan", 18, p);
System.out.println("学生s1的姓名:" + s1.name + "学生s1教授的姓名:" + s1.p.name + "," + "学生s1教授的年纪" + s1.p.age);// 学生1的教授
Student0 s2 = (Student0) s1.clone();
s2.p.name = "lisi ";
s2.p.age = 30;
s2.name = "z";
s2.age = 45;
System.out.println("修改S2的教授信息后:");
System.out.println("学生s1的姓名:" + s1.name + "学生s1教授的姓名:" + s1.p.name + "," + "学生s1教授的年纪" + s1.p.age);// 学生1的教授
}
}
运行结果:
学生s1的姓名:zhangsan学生s1教授的姓名:wangwu,学生s1教授的年纪50
修改S2的教授信息后:
学生s1的姓名:zhangsan学生s1教授的姓名:lisi ,学生s1教授的年纪30
可以看到,这里修改了S2的信息之后,S1的信息也发生了改变,那就说明S1和S2指向的是同一个professor0对象。这就说明,类中包含基本类型成员和非基本类型成员时,使用clone()方法结果是不一样的。
Java语言在重载clone()方法时存在浅复制和深复制之分。
当类中只有一些基本的数据类型时,采用上述的浅复制方法;
但是当类中包含了一些对象时,根据实际情况而言,我们希望只修改一个对象的成员对象时,另一个对象实例的成员不会发生改变,就需要用到深复制了,实现方法是在对对象调用clone()方法完成复制后,接着对对象中的非基本类型的属性也调用clone()方法完成深复制。示例如下:
class Professor implements Cloneable {
String name;
int age;
Professor(String name, int age) {
this.name = name;
this.age = age;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
}
class Student implements Cloneable {
String name;
int age;
Professor p;
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());
}
o.p = (Professor) p.clone();
return o;
}
}
public class DeepCopy {
public static void main(String args[]) {
long t1 = System.currentTimeMillis();
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;
System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age);// 学生1的教授不改变。
long t2 = System.currentTimeMillis();
System.out.println("name=" + s2.p.name + "," + "age=" + s2.p.age);// 学生2的教授改变。
System.out.println(t2-t1);
}
}
那么在编程时,如何选用复制方法呢?
首先检查类有无非基本类型(即对象)的数据成员。
若没有,则返回super.clone()即可;
若有,确保类中包含的所有非基本类型的成员变量都实现了深复制。
Object o = super.clone();//先执行浅复制
然后对每个对象attr执行以下语句:
o.attr = this.getAttr().clone();
最后返回o.
需要注意的是,clone()方法的保护机制在Object中clone()是被声明为protected的。以User类为例,通过声明为protected,就可以保证只有User类里面才能克隆User对象。
深复制和浅复制的区别
浅复制(Shallow Clone):被复制对象的所有变量都含有与原来对象相同的值,而所有对其他对象的引用仍然指向原来的对象。浅复制只考虑对象的值,而不考虑对象本身的引用。
深复制(Deep Clone):被复制的对象所有的变量都含有与原来对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制的新对象,而不再是指向原有的那些被引用的对象,即深复制把复制的对象所引用的对象都复制了一遍。