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):被复制的对象所有的变量都含有与原来对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制的新对象,而不再是指向原有的那些被引用的对象,即深复制把复制的对象所引用的对象都复制了一遍。