一、前言
拷贝的目的就是为了获得相同的对象,当需要属性值相同的对象时,不需要再重新一步一步创建
在Java中的拷贝需要实现Clonable接口,重写Object的clone()方法;同时在Java中的拷贝也分为浅拷贝和深拷贝,其两者的区别就在于对 对象中引用数据类型的不同处理方法。即:
浅拷贝就是克隆出来的新对象与原对象完全一致无二,包括引用类型字段的值。这就会有一个现象,针对引用类型的字段,两个对象的引用地址一致,如此一来新旧对象之间强关联,修改其中一个对象的内容,极可能影响到另外一个的内容。
当我们修改其中一个对象时,不会影响到另外一个。
二、浅拷贝
(一)概念
基本数据类型拷贝值,引用类型拷贝内存地址,修改基本数据类型不会对原对象产生影响,修改引用类型会对原对象产生影响。被拷贝对象通过实现
Cloneable
接口并 重写clone()
方法来实现浅拷贝。
(二)代码说明
public class Person implements Cloneable{
private String name;
private Integer age;
public String getName(){
return name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
public static void test01() throws CloneNotSupportedException {
Person p1=new Person();
p1.setName("小王");
p1.setAge(18);
Person p2=p1.clone();
System.out.println(p1+":"+p1.hashCode());
System.out.println(p2+":"+p2.hashCode());
}
运行结果:
Person{name='小王', age=18}:1324119927
Person{name='小王', age=18}:1915910607
通过上述代码 我们可以看出拷贝出的Person对象的存储哈希表上的哈希值不一样,说明两个对象的内存位置不同,即成功实现了对象的拷贝。下面的例子将进一步说明成员中引用类型哈希值的变化
Children类:实现Clonable接口
public class Children implements Cloneable{
private String name;
private Integer age;
public String getName(){
return name;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Children{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person类 其中存在Children类型的成员变量--->即引用类型变量
public class Person implements Cloneable{
private String name;
private Integer age;
private Children child;
public String getName(){
return name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setChild(Children child) {
this.child = child;
}
public Children getChild() {
return child;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public Person clone() throws CloneNotSupportedException {
// return (Person) super.clone();
Person clone=(Person)super.clone();//克隆Person对象
return clone;
}
}
//以下为测试类
public static void test02() throws CloneNotSupportedException {
Person p1=new Person();
p1.setName("小王");
p1.setAge(28);
Children children1=new Children();
children1.setName("张伟");
children1.setAge(5);
p1.setChild(children1);
Person p2=p1.clone();
System.out.println(p1+":对象的哈希值"+p1.hashCode()+":成员变量的哈希值"+p1.getChild().hashCode());
System.out.println(p2+":对象的哈希值"+p2.hashCode()+":成员变量的哈希值"+p1.getChild().hashCode());
}
测试结果:
Person{name='小王', age=28}:对象的哈希值990368553:成员变量的哈希值1096979270
Person{name='小王', age=28}:对象的哈希值2093176254:成员变量的哈希值1096979270
说明 浅拷贝的对象占用不同的内存位置,但引用类型的成员 哈希值一样 ,则说明两对象之间的变量会相互影响。任一个变量的变化会影响到另一个变量。
通过上述的实例,我们归纳以下步骤:
- 被拷贝对象的类实现Cloneable接口
- 被拷贝对象的类重写Object的clone方法
- 执行拷贝操作,就是调用重写的clone方法来完成克隆
三、深拷贝
(一)概念
深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。基础数据类型或者引用类型,对其中一个对象修改该值,不会影响另外一个。每个对象都需要实现
Cloneable
并重写clone()
方法,进而实现了对象的串行层层拷贝。
(二)代码说明
对Children类重写clone()方法:
@Override
protected Children clone() throws CloneNotSupportedException {
return (Children) super.clone();
对Person重写clone()方法:
@Override
public Person clone() throws CloneNotSupportedException {
// return (Person) super.clone();
Person clone=(Person)super.clone();//克隆Person对象
clone.setChild(child.clone()); //克隆Person中的成员变量 通过成员变量child的副本传参
return clone;
}
测试类:
public static void test04() throws CloneNotSupportedException {
Person p1=new Person();
p1.setName("小王");
p1.setAge(28);
Children children1=new Children();
children1.setName("张伟");
children1.setAge(5);
p1.setChild(children1);
Person p2=p1.clone(); //克隆person对象 同时也对Children成员变量进行了克隆 即两者完全剥离
System.out.println(p1.getChild());
System.out.println(p2.getChild());
children1.setName("张三");//单独设置children1的属性
System.out.println(p1.getChild());
System.out.println(p2.getChild());
Children children2=p2.getChild();//此时p2为p1的副本 ,用p2调用getChild方法来获取children对象
children2.setName("张无忌"); //单独设置children2的属性
System.out.println(p1.getChild());
System.out.println(p2.getChild());
//打印出两个子对象的哈希值 两者哈希值不一样说明 两个对象的存储位置不一样
System.out.println(p1.getChild().hashCode());
System.out.println(p2.getChild().hashCode());
}
Children{name='张伟', age=5}
Children{name='张伟', age=5}
Children{name='张三', age=5}
Children{name='张伟', age=5}
Children{name='张三', age=5}
Children{name='张无忌', age=5}
668386784
1329552164
从上述代码可以分析出,对对象进行深拷贝后,改变对象中引用类型数据的值 不会影响到另一个对象,通过最后两行的哈希值也可以比较出 两个子对象的哈希值是不相同的,这样就实现了对象间关系的完全脱离,以免对其他对象的内容造成影响。
深拷贝步骤:
先Children类实现接口并重新clone方法 返回本类的副本
后Person类重写clone方法,先克隆本类对象 ,再通过克隆的本类对象调用setChildren()方法传入克隆的children类,实现层层拷贝。