Java 对象的浅拷贝和深拷贝
一、概述
对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。:
- 浅拷贝: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝:深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
- 无论是浅拷贝还是深拷贝,都需要实现 clone() 方法,来完成操作。
二、浅拷贝
public class TestDemo{
public static void main(String[] args) {
Friend friend = new Friend("Ace","man",18);
// 原始对象
Person person = new Person("Jeffrey",18, friend);
System.out.println("原始对象: " + person);
// 拷贝对象
Person clonedPperson = (Person)person.clone();
System.out.println("拷贝对象: " + clonedPperson);
//原始对象和拷贝对象是否一样
System.out.println("原始对象和拷贝对象是否一样: " + (person == clonedPperson));
//修改基本类型
person.setAge(20);
System.out.println("修改基本类型后的对比:");
System.out.println("原始对象: " + person);
System.out.println("拷贝对象: " + clonedPperson);
//修改引用类型
person.getFriend().setAge(30);
System.out.println("修改引用类型后的对比:");
System.out.println("原始对象: " + person);
System.out.println("拷贝对象: " + clonedPperson);
}
}
class Person implements Cloneable{
private String name;
private int age;
private Friend friend;
public Person(String name, int age ,Friend friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public Friend getFriend() {
return friend;
}
@Override
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null ;
}
}
public String toString() {
return "Person={ " + "name=" + name + ", age=" + age + ", friend=" + friend+" }";
}
}
class Friend{
private String name;
private String sex;
private int age;
public Friend(String name,String sex,int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "{ Friend:" + "name=" + name + ", sex=" + sex + ", age=" + age + " }";
}
}
输出结果:
原始对象: Person={ name=Jeffrey, age=18, friend={ Friend:name=Ace, sex=man, age=18 } }
拷贝对象: Person={ name=Jeffrey, age=18, friend={ Friend:name=Ace, sex=man, age=18 } }
原始对象和拷贝对象是否一样: false
修改基本类型后的对比:
原始对象: Person={ name=Jeffrey,age=20
, friend={ Friend:name=Ace, sex=man, age=18 } }
拷贝对象: Person={ name=Jeffrey,age=18
, friend={ Friend:name=Ace, sex=man, age=18 } }
修改引用类型后的对比:
原始对象: Person={ name=Jeffrey, age=20, friend={ Friend:name=Ace, sex=man,age=30
} }
拷贝对象: Person={ name=Jeffrey, age=18, friend={ Friend:name=Ace, sex=man,age=30
} }
根据结果来看,通过Clonable接口并重写Object类的clone()
方法来赋值的对象,值类型的赋值不会改变,引用类型会改变,印证了只是赋值栈上面的数据和引用对象地址,最终指向堆的内存地址一致。
三、深拷贝
常用的方案有两种:
- 继续利用
clone()
方法,对其内的引用类型的变量,再进行一次clone()
(Friend实现),或者在clone()
创建新的引用变量赋值和新的对象 - 序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象。
1、clone
修改Pesron类,新创建一个对象返回:
@Override
public Object clone(){
// 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立
return new Person(name, age,new Friend(friend.name,friend.sex,friend.age));
}
2、序列化实现
具体参考Java 序列化,只是把FileOutputStream改成ByteArrayOutputStream。因为主要是FileOutputStream作用于文件,后者作用于byte[]。