理解Java对象克隆

简单的对象拷贝

在应用开发中,有时候需要得到一个对象的副本,然后对该副本做一些修改而不影响原始对象,可能大家会想到像下面示例中这样做。

假设定义一个Person对象,只有name和age两个字段。

public class Person {
    public String name;
    public int age;

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

按如下方法进行对象拷贝:

Person original = new Person("paul", 18);
        Person copy = original;
        copy.age = 20;

        System.out.println("original-name:" + original.name + ",age:" + original.age);
        System.out.println("copy-name:" + copy.name + ",age:" + copy.age);

输出结果:

original-name:paul,age:20
copy-name:paul,age:20

经测试发现,对拷贝对象做的修改,也导致原始对象发生了改变。其实拷贝变量与原始变量都指向了同一个引用,改变一个变量所引用的对象都将对另一个变量产生影响。我们可以打印original对象与copy对象的hashcode,就会发现它们的值相同,从而证明它们确实指向同一个引用,因此此法不通。

对象克隆

此时也许你想到了clone方法,但clone没法直接调用,它是Object的一个protected方法,可重新为Person类定义一个public的clone方法,具体如下:

public class Person implements Cloneable {
    public String name;
    public int age;

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

    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

注意还必须为Person类实现Cloneable接口,否则将报java.lang.CloneNotSupportedException异常。需要知道的是Cloneable接口是Java提供的几个标记接口之一,所谓标记接口,就是该接口没有方法让实现类来实现,使用该接口的唯一目的便是可以利用instanceof进行类型检验,如:

if (obj instanceof Cloneable)

此时我们再来克隆一个对象,继续测试:

Person original = new Person("paul", 18);
Person copy = null;
try {
    copy = original.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
copy.age = 20;

System.out.println("original-name:" + original.name + ",age:" + original.age);
System.out.println("copy-name:" + copy.name + ",age:" + copy.age);

输出结果:

original-name:paul,age:18
copy-name:paul,age:20

终于发现,当修改克隆得到的对象的年龄,不会对原始对象的年龄产生影响了,通过打印两个对象的hashcode,可知它们是两个不同的对象,也许此刻你觉得已经找到终极解决方案了!

但是,不要高兴得太早,没那么简单……

浅拷贝与深拷贝

假如当Person对象中又包含了子对象的引用,如下面的示例,为Person对象添加一个Pet字段:

public class Person implements Cloneable {
    public String name;
    public int age;
    public Pet pet;

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

    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

其中Pet类定义如下:

public class Pet {
    public String nickName;

    public Pet(String nickName) {
        this.nickName = nickName;
    }
}

再来看测试代码:

Person original = new Person("paul", 18);
original.pet = new Pet("mimi");

Person copy = null;
try {
    copy = original.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
copy.age = 20;
copy.pet.nickName = "beibei";

System.out.println("original-name:" + original.name + ",age:" + original.age + ",Pet nickName:" + original.pet.nickName);
System.out.println("copy-name:" + copy.name + ",age:" + copy.age + ",Pet nickName:" + copy.pet.nickName);

System.out.println("original-pet hashcode:" + original.pet.hashCode());
System.out.println("copy-pet hashcode:" + copy.pet.hashCode());
System.out.println("original hashcode:" + original.hashCode());
System.out.println("copy hashcode:" + copy.hashCode());

输出结果:

original-name:paul,age:18,Pet nickName:beibei
copy-name:paul,age:20,Pet nickName:beibei
original-pet hashcode:7643448
copy-pet hashcode:7643448
original hashcode:1761895363
copy hashcode:513775457

可见,通过克隆得到的Person对象的age字段的修改不会对原始对象产生影响,但当把克隆得到的Person对象的Pet的昵称从“mimi”改为了“beibei”,结果导致原始对象的Pet的昵称也变成了“beibei”,通过克隆对象与原始对象的Pet的hashcode值均为7643448可知,两个对象所引用的Pet其实是同一个对象。

到这里是否觉得有点乱了?clone()本身是Object的方法,而Object类其实对具体的类对象一无所知,所以就会将各个域进行对应的拷贝。如果对象中的所有数据域都属于数值或基本数据类型,这样的拷贝是没有问题的。但是如果对象中包含了对子对象的引用,拷贝的结果会将两个域引用到同一个子对象,导致原始对象与克隆对象共享这部分数据。

默认的克隆其实是浅拷贝(shallow copy),它不会克隆包含在对象中的内部对象,上述场景就是由于我们使用了浅拷贝。当然如果对象中的内部对象是不可变的,那么使用浅拷贝也无所谓,但是如果对象中的内部对象是可变的,如上述示例Person中的Pet,那么使用浅拷贝进行克隆就会出现问题。此时就需要我们重定义clone,实现所谓的深拷贝(deep copy)。

实现深拷贝的方案如下,首先为Pet类重定义clone方法:

public class Pet implements Cloneable {
    public String nickName;

    public Pet(String nickName) {
        this.nickName = nickName;
    }

    public Pet clone() throws CloneNotSupportedException {
        return (Pet) super.clone();
    }

}

然后改写Person中的clone方法:

public class Person implements Cloneable {
    public String name;
    public int age;
    public Pet pet;

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

    public Person clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        person.pet = pet.clone();

        return person;
    }
}

测试代码不变,依然如下:

Person original = new Person("paul", 18);
original.pet = new Pet("mimi");

Person copy = null;
try {
    copy = original.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
copy.age = 20;
copy.pet.nickName = "beibei";

System.out.println("original-name:" + original.name + ",age:" + original.age + ",Pet nickName:" + original.pet.nickName);
System.out.println("copy-name:" + copy.name + ",age:" + copy.age + ",Pet nickName:" + copy.pet.nickName);

System.out.println("original-pet hashcode:" + original.pet.hashCode());
System.out.println("copy-pet hashcode:" + copy.pet.hashCode());
System.out.println("original hashcode:" + original.hashCode());
System.out.println("copy hashcode:" + copy.hashCode());

输出结果:

original-name:paul,age:18,Pet nickName:mimi
copy-name:paul,age:20,Pet nickName:beibei
original-pet hashcode:179704568
copy-pet hashcode:932666694
original hashcode:7643448
copy hashcode:1761895363

可以看到,此时修改克隆对象的Pet的nickName,仅会影响到克隆对象,不会再影响原始对象的值了。再次对比克隆对象与原始对象的Pet的哈希值,可知现在它们已经是两个不同的对象了,也证明了我们实现了对子对象的深拷贝。

总结

其实克隆的应用场景并不多,但我们也应谨慎使用克隆,一旦为某个类实现了clone方法,那么就意味着可以为它克隆对象。另外如果需要实现深拷贝,那么还需要重新实现clone方法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值