Java深(Deep)拷贝与浅(Shadow)拷贝
来源:互联网 发布:matlab 无标度网络 编辑:程序博客网 时间:2024/06/05 11:08
- Java深Deep拷贝与浅Shadow拷贝
- 基本代码
- 浅拷贝
- 深拷贝
- 序列化实现深拷贝
- 不足之处缺点局限性
- 懒拷贝lazy copy
- 总结
Java深(Deep)拷贝与浅(Shadow)拷贝
基本代码
//代码清单1 Address.javaimport lombok.Data;import lombok.NoArgsConstructor;@Data@NoArgsConstructor//浅拷贝,则Adress不需要实现Cloneable接口public class Address implements Cloneable { private String country;/**国家*/ private String province;/**省*/ private String city;/**城市*/ public Address(String country,String province,String city){ this.country = country; this.province = province; this.city = city; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); }}
//代码清单2 Person.javaimport lombok.Data;import lombok.NoArgsConstructor;@Data@NoArgsConstructorpublic class Person implements Cloneable { private String name; private String gender; private Address address; public Person(String name, String gender, Address address) { this.name = name; this.gender = gender; this.address = address; } @Override protected Object clone() throws CloneNotSupportedException { /** * 浅拷贝 */ //return super.clone(); /** * 深拷贝 */ Person p = (Person)super.clone(); //把所有是引用类型的字段再拷贝一次 address = (Address) p.getAddress().clone(); return p; }}
浅拷贝
浅拷贝(shadow copy)即按位拷贝对象。新对象通过精确拷贝源对象值的方式被创建出来。假如对象具有引用类型的字段,那么只会拷贝引用地址(内存地址;reference addresses or memory address)。
如上图,结合代码清单1和2。在上图中person拥有字段name、gender和address,其中address是引用类型。对person进行浅拷贝后产生person2,person2仍然指向address。
经过观察发现,原始数据类型经过浅拷贝后,会生成新的一份数据,上图中是name->name1;gender->gender2。而address是一个Address对象,是引用类型,所以经过浅拷贝后仍旧会指向原来的address。
因此,person内的address经过任何改变,也会在person2中反映出来。
代码实现:
- 需要实现Cloneable接口。
- 重写clone()方法,返回super.clone();
//代码清单3public class Main { public static void main(String[] args) throws CloneNotSupportedException{ /**待拷贝的源对象*/ Address address = new Address("中国","广西省","崇左市"); Person person = new Person("张三","男",address); print("person => %s",person); /**拷贝*/ Person person2 = (Person)person.clone();//拷贝效果看Person的clone()方法的内容。 print("person2 => %s",person2); /** * 测试浅/深拷贝特性: * 1. 如果源拷贝对象有引用类型的字段,则改变引用类型的字段值,会既影响到源拷贝对象,又影响到clone后的对象 * 2. 改变原始类型字段值,则不会影响到双方 */ address.setProvince("湖南省"); address.setCity("长沙市"); person.setName("李四"); print("\n改变引用类型字段值后:"); print("person => %s",person); print("person2 => %s",person2); } public static void print(String format,Object... args){ System.out.println(String.format(format,args)); }}outputperson => Person(name=张三, gender=男, address=Address(country=中国, province=广西省, city=崇左市))person2 => Person(name=张三, gender=男, address=Address(country=中国, province=广西省, city=崇左市))改变引用类型字段值后:person => Person(name=李四, gender=男, address=Address(country=中国, province=湖南省, city=长沙市))person2 => Person(name=张三, gender=男, address=Address(country=中国, province=湖南省, city=长沙市))
观察输出,浅拷贝结论是:
- clone重写只需要返回super.clone();
- 源对象和拷贝对象内的引用数据类型字段指向的都是同一个地方。即该字段在任意一个地方改变,都会如实反映在各个拷贝或源对象中。
深拷贝
深拷贝拷贝所有的字段(Fields),并且拷贝字段指向的动态分类的内存。当一个对象连同它指向的对象被拷贝时,深拷贝就发生了。
在上图中,person拥有字段name、gender、address。当对person进行深拷贝时,name1包含复制name的值,同理gender1和address1都包含复制的gender和address值。address发生任何改变,都不会在address1内发生改变。
代码实现:
- 需要实现Cloneable接口(源对象内的引用类型也许要实现Cloneable接口,并重写clone()方法)。
- 重写clone()方法。先调用要拷贝对象的clone()方法,然后在调用源对象内引用对象的clone()方法。
1. 如代码清单2,下面是一些片段 /** * 深拷贝 */ Person p = (Person)super.clone(); //把所有是引用类型的字段再拷贝一次 address = (Address) p.getAddress().clone(); return p;2. 测试代码和代码清单3一模一样outputperson => Person(name=张三, gender=男, address=Address(country=中国, province=广西省, city=崇左市))person2 => Person(name=张三, gender=男, address=Address(country=中国, province=广西省, city=崇左市))改变引用类型字段值后:person => Person(name=李四, gender=男, address=Address(country=中国, province=广西省, city=崇左市))person2 => Person(name=张三, gender=男, address=Address(country=中国, province=湖南省, city=长沙市))
深拷贝结论:
- 源对象和拷贝对象之间互不影响。无论字段是否是应用类型还是原始数据类型。
序列化实现深拷贝
注意,对象内的所有类都必须实现序列化接口。
1. 代码清单1、2中 Address.java 、Person.java需要实现Serializable接口//测试代码如下public class Main { public static void main(String[] args) throws IOException { ObjectOutputStream oos = null; ObjectInputStream ois = null; try { // create original serializable object Address address = new Address("中国", "广西省", "崇左市"); Person person = new Person("张三", "男", address); // print it print("person => %s", person); // deep copy ByteArrayOutputStream bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); // serialize and pass the object oos.writeObject(person); oos.flush(); ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bin); // return the new object Person person2 = (Person) ois.readObject(); // verify it is the same print("person2 => %s", person2); // change the original object's contents address.setProvince("湖南省"); address.setCity("长沙市"); person.setName("李四"); // see what is in each one now print("\n改变引用类型字段值后:"); print("person => %s", person); print("person2 => %s", person2); } catch (Exception e) { System.out.println("Exception in main = " + e); } finally { oos.close(); ois.close(); } } public static void print(String format,Object... args){ System.out.println(String.format(format,args)); }}outputperson => Person(name=张三, gender=男, address=Address(country=中国, province=广西省, city=崇左市))person2 => Person(name=张三, gender=男, address=Address(country=中国, province=广西省, city=崇左市))改变引用类型字段值后:person => Person(name=李四, gender=男, address=Address(country=中国, province=湖南省, city=长沙市))person2 => Person(name=张三, gender=男, address=Address(country=中国, province=广西省, city=崇左市))
不足之处(缺点、局限性)
- 不能序列化一个transient变量。
- 如果是单例模式,你以这种方式进行深拷贝,则它就不再是单例模式了。
- 性能问题。创建一个socket,序列化一个对象,然后通过socket传递它,最后反序列化。不建议这样用,因为它比实现Cloneable慢100倍左右。
懒拷贝(lazy copy)
懒拷贝实际上是深拷贝和浅拷贝的结合体。当初始化的时候,用浅拷贝。一个计数器需要用来追踪有多少个对象共享数据,当应用更改源对象时,它知道数据是否共享,并决定是否要做深拷贝。
懒拷贝从结果看,像极了深拷贝,只是它充分利用了浅拷贝的优势——速度快。当源对象内的引用数据类型字段不经常被更改时,用浅拷贝。
缺点是追踪counter的花销,此外,在特殊情况下,循环引用也会导致问题。
总结
使用场景有:
使用浅拷贝:对象只有原始数据类型字段;有引用类型的字段,但从不更改它。
使用深拷贝:有引用类型的字段,且经常被修改。
简言之,使用哪种拷贝方式看需求如何。
其实也可以手动进行深拷贝,或者浅拷贝,即
import org.springframework.beans.BeanUtils//Copy the property values of the given source bean into the target bean. public static void copyProperties(Object source, Object target) throws BeansException { copyProperties(source, target, null, (String[]) null); }
参考来源
- Java深拷贝与浅拷贝
- Java深拷贝与浅拷贝
- Java深拷贝与浅拷贝
- Java深(Deep)拷贝与浅(Shadow)拷贝
- IOS 深拷贝和浅拷贝问题 (deep copy and shadow copy)
- 深拷贝与浅拷贝(Deep Copy and Shallow Copy)
- 深拷贝与浅拷贝(Deep Copy and Shallow Copy)
- iOS 浅拷贝(Shallow Copy)与深拷贝(Deep Copy)
- [转]QImage的浅拷贝与深拷贝 -- Deep Copy
- Java中深拷贝(Deep Clone)与浅拷贝(Shallow Clone)
- java浅拷贝(shallow clone)与深拷贝(deep clone)
- Java中深拷贝与浅拷贝
- JAVA中深拷贝与浅拷贝
- java中深拷贝与浅拷贝。
- Java深拷贝与浅拷贝原理
- Java 数组 浅拷贝与深拷贝
- Java 数组 浅拷贝与深拷贝
- java中的深拷贝与浅拷贝
- Java 深拷贝与浅拷贝
- java浅拷贝与深拷贝
- JAVA中浅拷贝与深拷贝
- js阻止默认行为
- HDU
- 应对分布式缓存宕机的方案
- HGDB与oracle中dual的区别
- Java设计模式整理
- Java深(Deep)拷贝与浅(Shadow)拷贝
- 百度地图调用实例
- 算法day06
- HDU
- 类加载
- 类之构造函数与析构函数
- 剑指offer之二---替换空格
- JNI(一) 认识JNI 中 (cmake快速配置+jni简单运用)
- Wannafly模拟赛2: A. Contest(Cdq分治)