java基础之深复制与浅复制

      今天修改了以前代码中关于对象做为参数传递时造成的bug,经查看代码及分析总后发现我们的需求应该是通过java深度拷贝技术才能实现。因此本文将复习总结下java拷贝相关知识。首先我们看下面的代码。
public class TestMethodInvokeRef {
public static void main(String[] args) {
User user = new User();
user.setRoleId(1);
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one", 1);
map.put("two", 2);
user.setMap(map);
User user2 = new User();
user2.setMap(user.getMap());
//User tmpUser = (User) user.deepClone();
LoginInfo info = new LoginInfo();
info.changeValue(user.getMap());
System.out.println("user的map值:");
print(user.getMap());
System.out.println("user2的map值:");
print(user2.getMap());
}

private static void print(Map<String, Integer> map) {
if (null != map) {
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + "\t" + value);
}
}
}
}

class User implements Serializable {

private static final long serialVersionUID = 1L;

private Map<String, Integer> map = new HashMap<String, Integer>();

private int roleId = 0;

public User() {
// TODO Auto-generated constructor stub
}

public User(User user) {
this.map = user.map;
this.roleId = user.roleId;
}

public Map<String, Integer> getMap() {
return map;
}

public void setMap(Map<String, Integer> map) {
this.map = map;
}

public int getRoleId() {
return roleId;
}

public void setRoleId(int roleId) {
this.roleId = roleId;
}
}

class LoginInfo {

public void changeValue(Map<String, Integer> map) {
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
value = value * 10;
map.put(key, value);
}
}
}

输出结果:
user的map值:
two 20
one 10
user2的map值:
two 20
one 10
期望输出结果:
user的map值:
two 2
one 1
user2的map值:
two 2
one 1
       上面程序想实现的功能:将一个user的属性map的值传递给一个调用接口去处理(这里用一个函数来模拟),同时将这个map再传递给另一个调用接口,很明显需求的本来目的是想将这个map传过去后两个接口所做处理互不干扰,而打印结果却让我们很失望,通过我前面一篇文章<<java基础之函数参数传递调用>>很容易分析出上面这种实现方式问题所在。那么如何让一个对象的多个副本之间相互独立,一个副本的修改不影响别一个副本呢?这里用java复制(克隆)技术很简单就能实现这个目的。java中复制分为浅复制和深度复制:
(1).浅复制(浅克隆)
       被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
(2).深复制(深克隆)
       被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

克隆对象与被克隆对象之间的关系:
①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。

如何实现克隆:
①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
②在派生类中覆盖基类的clone()方法,并声明为public。
③在派生类的clone()方法中,调用super.clone()。
④在派生类中实现Cloneable接口。

请看下面的程序:
public class TestShallowClone {
public static void main(String[] args) {
Student s1 = new Student("zhangsan", 18);
Student s2 = (Student) s1.clone();
s2.name = "lisi";
s2.age = 20;
System.out.println("name=" + s1.name + "," + "age=" + s1.age);// 修改学生2后,不影响学生1的值。
}
}

class Student implements Cloneable {
String name;
int age;

Student(String name, int age) {
this.name = name;
this.age = age;

}


public Object clone() {
Object o = null;
try {
o = (Student) super.clone();// Object中的clone()识别出你要复制的是哪一个对象。
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
}
输出结果为:
name=zhangsan,age=18
      可以看到通过克隆后将s2.name = "lisi";s2.age = 20;不影响s1相应的值。但上面的克隆的对象Student类中并没有引用其它对象的引用变量(String类是final),若Student类中有其它对象的
引用变量通过上在的克隆还会出现s2的变化不影响s1吗?请看下面的程序:
public class TestShallowClone {
public static void main(String[] args) {
Professor p = new Professor("wangwu", 50);
Student s1 = new Student("zhangsan", 18, p);
Student s2 = (Student) s1.clone();
s2.name = "lisi";
s2.age = 30;
s2.p.name = "prof.lisi";
s2.p.age = 60;
System.out.println("s1.name=" + s1.name + " s1.age=" + s1.age
+ " s1.professor.name=" + s1.p.name + "," + " s1.professor.age=" + s1.p.age);// 学生1的教授成为lisi,age为30
}
}

class Student implements Cloneable {
String name;// 常量对象。
int age;
Professor p;// 学生1和学生2的引用值都是一样的。

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());
}
return o;
}
}

class Professor {
String name;
int age;

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

}
输出结果:
s1.name=zhangsan s1.age=18 s1.professor.name=prof.lisi, s1.professor.age=60

输出显示s2对nama和age基本属性变量的更改并没有影响s1对应的值,但s2对引用变量Professor的更改时s1也同时变化了。

这里可能你有两个疑问:

①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢? 在运行时刻,Object中的 clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
②为什么实现了Cloneable接口,克隆对象中引用变量的变化仍影响原对象呢?这是因为克隆后所有的对其他对象的引用仍然指向原来的对象,所以对克隆后的对象更改其引用变量仍会影响原来对象中的引用。如果让克隆后的对象中引用变量的更改不影响原

对象中的引用呢?这里就是深度克隆的作用了

深度克隆有两种实现方式:
一.类implements Cloneable,然后重写clone()方法,在clone()方法中调用super.clone(),然后还要对引用型变量所指的对象进行克隆(前提是该对象也可以被克隆即实现了Cloneable接口):在本例中让被引用变量的类(Professor)和含有引用的类(Student)都实现克隆,同时在有引用类(Student)中实现clone方法时将引用变量(prefessor)也显示克隆。

二.序列化:将该对象写出到对象输出流,那么用对象输入流读回的对象就是原对象的一个深度克隆(前提条件是二者都可序列化即实现了Serializable接口)。


下面分别用这两种方式来实现深度克隆
1.让引用对象也实现克隆来达到深度克隆的目的:
public class TestShallowClone {
public static void main(String[] args) {
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的教授 //成为lisi,age为30
}
}

class Student implements Cloneable {
String name;// 常量对象。
int age;
Professor p;// 学生1和学生2的引用值都是一样的。

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();
o.p = (Professor) p.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
}

class Professor implements Cloneable {
String name;
int age;

Professor(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
输出结果为:
s1.name=zhangsan s1.age=18 s1.professor.name=wangwu, s1.professor.age=50
可见深度复制后,s2的修改不会影响s1。

       通过上面和程序可以得出:所谓深度克隆其实就是将所有的引用变量也进行显示克隆操作,前提是所引用变量的类实现了Cloneable接口,实现了clone方法。若引用变量没有实现Cloneable接口该如何实现深度克隆呢?这就是深度克隆的第二种实现方式:串行化技术实现深度克隆。


2.串行化技术实现深度克隆:把对象写到流里的过程是串行化(Serilization)过程,在行业内又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。


可以通过串行化来实现深度复制的前提条件是:被引用对象是可串行化的即实现了Serializable接口,而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。在后续我会专门写一篇文章来总结java串行化相关知识。


public class TestShallowClone {
public static void main(String[] args) {
Professor p = new Professor("wangwu", 50);
Student s1 = new Student("zhangsan", 18, p);
Student s2 = (Student) s1.deepClone();
s2.name = "lisi";
s2.age = 30;
s2.p.name = "prof.lisi";
s2.p.age = 60;
System.out.println("s1.name=" + s1.name + " s1.age=" + s1.age
+ " s1.professor.name=" + s1.p.name + "," + " s1.professor.age=" + s1.p.age);// 学生1的教授成为lisi,age为30
}
}

class Student implements Serializable {
private static final long serialVersionUID = 1L;
String name;// 常量对象。
int age;
Professor p;// 学生1和学生2的引用值都是一样的。

Student(String name, int age, Professor p) {
this.name = name;
this.age = age;
this.p = p;
}

public Student deepClone() {

try {
//将对象写入到流中
ByteArrayOutputStream btaOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btaOut);
objOut.writeObject(this);
//从流中读取对象生成一个新对象
ByteArrayInputStream btaIn = new ByteArrayInputStream(btaOut.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(btaIn);
return (Student)objIn.readObject();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
}

class Professor implements Serializable {

private static final long serialVersionUID = 1L;
String name;
int age;

Professor(String name, int age) {
this.name = name;
this.age = age;
}
}
输出结果:
s1.name=zhangsan s1.age=18 s1.professor.name=wangwu, s1.professor.age=50
     至此java深度复制与浅复制介绍完毕。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值