Java的浅拷贝和深拷贝
2016-01-20 16:52
483 查看
浅拷贝:
假如说你想复制一个简单变量。很简单:
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
但是如果你复制的是一个对象,情况就有些复杂了。
假设说我是一个初学者,我会这样写:
打印结果:
这里我们自定义了一个学生类,该类只有一个number字段。
我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,
难道真的是这样吗?
我们试着改变stu2实例的number字段,再打印结果看看:
打印结果:
这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?
原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,
这样,stu1和stu2指向内存堆中同一个对象。如图:
那么,怎样才能达到复制一个对象呢?
是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。
该方法的签名是:
protected native Object clone() throws CloneNotSupportedException;
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。
一般步骤是(浅复制):
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)
下面对上面那个方法进行改造:
打印结果:
深拷贝:
上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):
我们在学生类里再加一个Address类。
打印结果:
我们在main方法中试着改变addr实例的地址。
打印结果:
这就奇怪了,怎么两个学生的地址都改变了?
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:
打印结果:
还有如果要复制的一个类中有很多个其他的对象属性,则可以用序列化和反序列化来进行深拷贝:
如何利用序列化来完成对象的拷贝呢?在内存中通过字节流的拷贝是比较容易实现的。把母对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与母对象之间并不存在引用共享的问题,真正实现对象的深拷贝。
所以使用该工具类的对象只要实现Serializable接口就可实现对象的克隆,无须继承Cloneable接口实现clone()方法。
参考博客:
点击打开链接
点击打开链接
假如说你想复制一个简单变量。很简单:
int apples = 5; int pears = apples;
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
但是如果你复制的是一个对象,情况就有些复杂了。
假设说我是一个初学者,我会这样写:
class Student { private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = stu1; System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); } }
打印结果:
学生1:12345 学生2:12345
这里我们自定义了一个学生类,该类只有一个number字段。
我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,
难道真的是这样吗?
我们试着改变stu2实例的number字段,再打印结果看看:
stu2.setNumber(54321); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber());
打印结果:
学生1:54321 学生2:54321
这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?
原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,
这样,stu1和stu2指向内存堆中同一个对象。如图:
那么,怎样才能达到复制一个对象呢?
是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。
该方法的签名是:
protected native Object clone() throws CloneNotSupportedException;
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。
一般步骤是(浅复制):
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)
下面对上面那个方法进行改造:
class Student implements Cloneable{ private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); stu2.setNumber(54321); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); } }
打印结果:
学生1:12345 学生2:12345学生1:12345
学生2:54321
深拷贝:
上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):
我们在学生类里再加一个Address类。
class Address { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
打印结果:
学生1:123,地址:杭州市 学生2:123,地址:杭州市乍一看没什么问题,真的是这样吗?
我们在main方法中试着改变addr实例的地址。
addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
打印结果:
学生1:123,地址:杭州市 学生2:123,地址:杭州市学生1:123,地址:西湖区
学生2:123,地址:西湖区
这就奇怪了,怎么两个学生的地址都改变了?
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:
package abc; class Address implements Cloneable { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } @Override public Object clone() { Address addr = null; try{ addr = (Address)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return addr; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); //浅复制 }catch(CloneNotSupportedException e) { e.printStackTrace(); } stu.addr = (Address)addr.clone(); //深度复制 return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
打印结果:
学生1:123,地址:杭州市 学生2:123,地址:杭州市学生1:123,地址:西湖区
学生2:123,地址:杭州市
还有如果要复制的一个类中有很多个其他的对象属性,则可以用序列化和反序列化来进行深拷贝:
利用序列化实现对象的拷贝
如何利用序列化来完成对象的拷贝呢?在内存中通过字节流的拷贝是比较容易实现的。把母对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与母对象之间并不存在引用共享的问题,真正实现对象的深拷贝。public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj){ T cloneObj = null; try { //写入字节流 ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new ObjectOutputStream(out); obs.writeObject(obj); obs.close(); //分配内存,写入原始对象,生成新对象 ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(ios); //返回生成的新对象 cloneObj = (T) ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; } }使用该工具类的对象必须要实现Serializable接口,否则是没有办法实现克隆的。
public class Person implements Serializable{ private static final long serialVersionUID = 2631590509760908280L; .................. //去除clone()方法 } public class Email implements Serializable{ private static final long serialVersionUID = 1267293988171991494L; .................... }
所以使用该工具类的对象只要实现Serializable接口就可实现对象的克隆,无须继承Cloneable接口实现clone()方法。
public class Client { public static void main(String[] args) { //写封邮件 Email email = new Email("请参加会议","请与今天12:30到二会议室参加会议..."); Person person1 = new Person("张三",email); Person person2 = CloneUtils.clone(person1); person2.setName("李四"); Person person3 = CloneUtils.clone(person1); person3.setName("王五"); person1.getEmail().setContent("请与今天12:00到二会议室参加会议..."); System.out.println(person1.getName() + "的邮件内容是:" + person1.getEmail().getContent()); System.out.println(person2.getName() + "的邮件内容是:" + person2.getEmail().getContent()); System.out.println(person3.getName() + "的邮件内容是:" + person3.getEmail().getContent()); } } ------------------- Output: 张三的邮件内容是:请与今天12:00到二会议室参加会议... 李四的邮件内容是:请与今天12:30到二会议室参加会议... 王五的邮件内容是:请与今天12:30到二会议室参加会议...
参考博客:
点击打开链接
点击打开链接
相关文章推荐
- 浅谈Java中的hashcode方法
- J2EE引擎出现java.lang.UnsatisfiedLinkError: CC错误的原因
- Spring整合JMS(二)——三种消息监听器
- Spring监听配置的几种方式
- HeadFirst Java 1-20
- Java代码发送邮件
- springboot备用记录
- java.util.ConcurrentModificationException异常解决
- MyEclipse使用技巧之通过断点看值是否存入action中
- Error:java: Compilation failed: internal java compiler error
- java基础知识点梳理2
- java.lang.OutOfMemoryError: PermGen space 解决办法
- Java基于JavaMail实现向QQ邮箱发送邮件
- Run-Time Data Areas 运行时数据区
- JavaSE_Java字符串与日期类型 相互转换
- java基础知识点梳理1
- 使用Spring mvc接收整个url地址及参数时注意事项
- struts配置文件的formbean
- Spring整合JMS(一)——基于ActiveMQ实现
- spring jdbcTemplate 做持久层 并对其进行优化