Java 对象克隆——Object.clone()

设置字体大小:

Java中对象的传递都是引用传递,那么如果我们只是想将一个对象的属性值复制给另一个对象,就可以使用

protected Object clone() throws CloneNotSupportedException

来实现一个对象的复制。下面就简单说一说 clone() 方法的使用。

浅复制

当一个类中的属性是普通的数据类型,不包含其他类的对象时调用 clone() 函数就是执行浅复制。然而,要复制一个类的对象还要说明该类是可以被复制的,方法就是让该类实现一个标记接口“Cloneable”,该接口中没有实际的方法和属性,只是作为一个标记,说明该类是可以被复制的。例如:

    public class BaseClass implements Cloneable
    {
        // Rest of the class.
        // Notice that you don't even have to write the clone method!
    }

如果一个类没有实现该接口就直接使用 clone() 方法,会抛出“CloneNotSupportedException”的异常。

当一个类实现了“Cloneable”接口后,就可以重写(override) Object 类的 clone() 方法来实现该类的克隆操作。希望在文章开头部分你就注意到该方法是被 “protect” 修饰的。因此在重写时只能使用比 “protect” 更高的权限。一般的写法如下:

    public class BaseClass implements Cloneable
    {
        /* Rest of the class.*/
        public Object clone () 
         throws CloneNotSupportedException
        {
            return super.clone();
        }
    }

希望你能注意到,“clone()” 方法的返回值类型是 Object 类,因此在调用这个方法的时候往往需要做一个类型转化。

一个小例子:

class Person implements Cloneable {  /*实现 Cloneable 接口,说明该类可以被克隆。该接口为空接口,里面没有定义方法和属性,所以不需要实例化该接口。*/
    String name;
    int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override  /*覆盖父类 Object 中的 clone() 方法*/
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  /*调用 Object 类的 clone() 方法,返回该对象的副本(是 Object 类的对象)*/
    }

    public void getPersonInfo() {
        System.out.println("Name: "+this.name+" Age:"+this.age);
    }
}
public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
	Person pA = new Person("张三",20);
        System.out.println("pA 的信息:");
        pA.getPersonInfo();
	Person pB = (Person) pA.clone();  /*克隆 pA 对象给 pB 需要注意的是 clone() 方法返回的是一个 Object 类对象,需要向下转化为 Person 类对象*/
        System.out.println("克隆后 pB 的信息:");
	pB.getPersonInfo(); /*输出 pB 对象的信息,和 pA 对象信息相同*/
        System.out.println("改变 pB 对象 age 的值为 30");
	pB.age = 30; /*更改 pB 对象中 age 属性值*/
        System.out.println("修改后 pB 的信息如下(已经改变):");
	pB.getPersonInfo(); /*pB 对象中 age 属性值已经改变*/
        System.out.println("pA 的信息如下(还是原来的值):");
	pA.getPersonInfo(); /*pA 对象中的 age 属性值没有被改变,说明 pB 不是 pA 的引用,而是复制*/
    }
}

运行结果如下:

pA 的信息:
Name: 张三 Age:20
克隆后 pB 的信息:
Name: 张三 Age:20
改变 pB 对象 age 的值为 30
修改后 pB 的信息如下(已经改变):
Name: 张三 Age:30
pA 的信息如下(还是原来的值):
Name: 张三 Age:20

深复制

当要被克隆的类中的属性含有其他类(或者本类)的对象时,这时的克隆操作就比浅复制更深了,需要对调用 super.clone() 方法返回的结果进行二次开发,否则,复制后的副本和原本其中的引用对象属性值引用的是同一个对象。副本对该引用对象属性进行的改变会影响到原本的引用对象属性值。下面给出一个小例子来说明该问题:

class BirthDay {
    String year;
    String month;
    String day;

    public BirthDay(String year, String month, String day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public BirthDay() {
    }

    public String toString() {
        return this.year + "-" + this.month + "-" + this.day;
    }
}
class Person implements Cloneable {
    String name;
    int age;
    BirthDay birthDay;

    public Person() {
    }

    public Person(String name, int age, BirthDay birthDay) {
        this.name = name;
        this.age = age;
        this.birthDay = birthDay;
    }

    @Override 
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void getPersonInfo() {
        System.out.println("Name: "+this.name+" Age:"+this.age + " Birthday: " + this.birthDay);
    }
}
public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Person pA = new Person("张三", 20, new BirthDay("1990", "01", "01"));
        pA.getPersonInfo();
        Person pB = (Person) pA.clone();
        pB.getPersonInfo();
        pA.birthDay.year = "2000"; /*修改 pA 中 birthDay 属性值*/
        pB.getPersonInfo(); /*查看 pB 的信息*/
    }
}

运行结果:

Name: 张三 Age:20 Birthday: 1990-01-01
Name: 张三 Age:20 Birthday: 1990-01-01
Name: 张三 Age:20 Birthday: 2000-01-01

可以看到,当修改了 pA 中 birthDay 属性值后,pB 的 birthDay 属性值也跟着改变了,原因就是 pB 直接复制了 birthDay 对象的引用,而不是它的值。我们很多时候是想让复制后的两个对象是完全独立的,这就需要深复制了。

深复制怎么用呢?其实很简单,将 BirthDay 类也标记为可复制的,并重写 clone() 方法,然后在 Person 类中的 clone() 方法中调用 BirthDay 类的 clone() 方法,实现 birthDay 对象的独立克隆操作,修正后的示例如下:

class BirthDay implements Cloneable {
    String year;
    String month;
    String day;

    public BirthDay(String year, String month, String day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public BirthDay() {
    }
    @Override  /*BirthDay 类也重写 clone() 方法 */
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public String toString() {
        return this.year + "-" + this.month + "-" + this.day;
    }
}
class Person implements Cloneable {
    String name;
    int age;
    BirthDay birthDay;

    public Person() {
    }

    public Person(String name, int age, BirthDay birthDay) {
        this.name = name;
        this.age = age;
        this.birthDay = birthDay;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person newPerson = (Person) super.clone();
        if (this.birthDay != null)
            newPerson.birthDay = (BirthDay) this.birthDay.clone(); /*调用 birthDay 对象的 clone() 方法,创建 birthDay 对象的独立副本 */
        return newPerson;
    }

    public void getPersonInfo() {
        System.out.println("Name: "+this.name+" Age:"+this.age + " Birthday: " + this.birthDay);
    }
}
public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Person pA = new Person("张三", 20, new BirthDay("1990", "01", "01"));
        System.out.println("pA 的信息:");
        pA.getPersonInfo();
        Person pB = (Person) pA.clone();
        System.out.println("克隆后 pB 的信息:");
        pB.getPersonInfo();
        System.out.println("修改 pA 中 birthDay 属性");
        pA.birthDay.year = "2000";
        System.out.println("修改 pA 后 pB 的信息:");
        pB.getPersonInfo();
        System.out.println("修改 pA 后 pA 的信息:");
        pA.getPersonInfo();
    }
}

运行结果如下:

pA 的信息:
Name: 张三 Age:20 Birthday: 1990-01-01
克隆后 pB 的信息:
Name: 张三 Age:20 Birthday: 1990-01-01
修改 pA 中 birthDay 属性
修改 pA 后 pB 的信息:
Name: 张三 Age:20 Birthday: 1990-01-01
修改 pA 后 pA 的信息:
Name: 张三 Age:20 Birthday: 2000-01-01

可以看出,副本 pB 和原本 pA 已经是相互独立的了。

需要注意的问题

1、不要搞很复杂的深复制,例如一层套一层的对象引用,这样会造成很大的资源开销。

2、当一个类作为一个类库被其他类使用时,最好将该类标记为可复制的,防止其他类在导入该类时,想要使用克隆功能却抛出异常。

 

参考链接:

Wikipedia:clone (Java method)

How to avoid traps and correctly override methods from java.lang.Object


赞 (0)   -->微信赞赏<--

微信扫描下方左侧二维码或搜索“sunriseydy”关注我的公众号,便捷地阅读博客内容,订阅博客更新
也可以扫描下方右侧的小程序码,进入我的微信小程序:“sunriseydy”,在手机上阅读文章

      

版权说明:

知识共享许可协议
作品 sunriseydy 采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
文章内容如未说明均为原创,欢迎转载,但请注明原作者和出处。部分来自互联网的文章,如有侵权,请联系我,24小时内删除,谢谢
Email:i@mail.sunriseydy.top

评论一下呗亲

电子邮件地址不会被公开。 必填项已用*标注

添加表情