Java中的浅拷贝与深拷贝
声明: 以下内容来源于“Java技术指北” 原文地址:https://mp.weixin.qq.com/s/SQB4ZteKe7-fHFzL__6cSQ 学习整理;
Java中的浅拷贝与深拷贝
1. 创建对象的5种方式
①、通过 new 关键字
这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象。比如 Object obj = new Object();
②、通过 Class 类的 newInstance() 方法
这种默认是调用类的无参构造方法创建对象。比如 Person p2 = (Person) Class.forName("com.ys.test.Person").newInstance();
③、通过 Constructor 类的 newInstance 方法
这和第二种方法类都是通过反射来实现。通过 java.lang.relect.Constructor 类的 newInstance() 方法指定某个构造器来创建对象。
Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
实际上第二种方法利用 Class 的 newInstance() 方法创建对象,其内部调用还是 Constructor 的 newInstance() 方法。
④、利用 Clone 方法
Clone 是 Object 类中的一个方法,通过 对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B,clone 克隆,顾名思义就是创建一个一模一样的对象出来。
Person p4 = (Person) p3.clone();
⑤、反序列化
序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。
2. Clone方法
本篇博客我们讲解的是 Java 的深拷贝和浅拷贝,其实现方式正是通过调用 Object 类的 clone() 方法来完成。在 Object.class 类中,源码为:
protected native Object clone() throws CloneNotSupportedException;
这是一个用 native 关键字修饰的方法,关于native关键字不理解也没关系,只需要知道用 native 修饰的方法就是告诉操作系统,这个方法我不实现了,让操作系统去实现。具体怎么实现我们不需要了解,只需要知道 clone方法的作用就是复制对象,产生一个新的对象。
3. 基本类型和引用类型
这里再给大家普及一个概念,在 Java 中基本类型和引用类型的区别。
在 Java 中数据类型可以分为两大类:基本类型和引用类型。
基本类型也称为值类型,分别是字符类型 char,布尔类型 boolean以及数值类型 byte、short、int、long、float、double。
引用类型则包括类、接口、数组、枚举等。
Java 将内存空间分为堆和栈。基本类型直接在栈中存储数值,而引用类型是将引用放在栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。
4. 浅拷贝
这是一个我们要进行赋值的原始类 Person。下面我们产生一个 Person 对象,并调用其 clone 方法复制一个新的对象。
注意:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法。
首先看原始类 Person 实现 Cloneable 接口,并且覆写 clone 方法,它还有三个属性,一个引用类型 String定义的 name,一个基本类型 int定义的 age,还有一个引用类型 Address ,这是一个自定义类,这个类也包含两个属性 provices 和 city 。
代码中我们只是更改了克隆对象 p2 的属性 Address 为四川省成都市(原对象 p1 是广东省深圳市) ,但是从打印结果来看,原对象 p1 和克隆对象 p2 的 Address 属性都被修改了。
也就是说对象 Person 的属性 Address,经过 clone 之后,其实只是复制了其引用,他们指向的还是同一块堆内存空间,当修改其中一个对象的属性 Address,另一个也会跟着变化。
package com.linwei.test; public class TestForPractice { public static void main(String[] args) throws Exception{ Person p1 = new Person("zhangsan",18); p1.setAddress("广东", "深圳"); System.out.println("p1="+p1); Person p2 = (Person)p1.clone(); //浅拷贝 System.out.println("p2="+p2); p2.setAddress("四川", "成都"); p2.name = "lisi"; p2.age = 20; System.out.println("-------浅拷贝后再次修改p2对象--------"); System.out.println("p1="+p1); System.out.println("p2="+p2); } } class Person implements Cloneable{ public String name; public int age; public Address address; public Person() {} public Person(String name, int age) { super(); this.name = name; this.age = age; this.address = new Address(); } /** * 调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法; * * clone的默认是对象的浅拷贝,即如果字段是值类型的,那么对该字段执行复制;(String比较特殊,虽然它是引用类型但它也是复制的值而不是引用,这点要注意) * 如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。 */ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public void setAddress(String provices,String city) { address.setAddress(provices, city); } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", address=" + address + "]"; } } class Address { private String provices; private String city; public void setAddress(String provices,String city) { this.provices = provices; this.city = city; } @Override public String toString() { return "Address [provices=" + provices + ", city=" + city + "]"; } public String getProvices() { return provices; } public void setProvices(String provices) { this.provices = provices; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
5. 深拷贝
弄清楚了浅拷贝,那么深拷贝就很容易理解了。
深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。
深拷贝的原理我们知道了,就是要让原始对象和克隆之后的对象所具有的引用类型属性不是指向同一块堆内存,这里有两种实现思路。
5.1 让每个引用类型属性内部都重写clone() 方法
但是这种做法有个弊端,这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
public class Address implements Cloneable{ private String provices; private String city; public void setAddress(String provices,String city){ this.provices = provices; this.city = city; } @Override public String toString() { return "Address [provices=" + provices + ", city=" + city + "]"; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
Person.class 的 clone() 方法:
@Override protected Object clone() throws CloneNotSupportedException { Person p = (Person) super.clone(); p.address = (Address) address.clone(); return p; }
5.2 利用序列化
序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。
注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。
package com.linwei.test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class TestForPractice { public static void main(String[] args) throws Exception{ Person p1 = new Person("zhangsan",18); p1.setAddress("广东", "深圳"); System.out.println("p1="+p1); Person p2 = (Person)p1.deepClone(); //深拷贝 System.out.println("p2="+p2); p2.setAddress("四川", "成都"); p2.name = "lisi"; p2.age = 20; System.out.println("-------深拷贝后再次修改p2对象--------"); System.out.println("p1="+p1); System.out.println("p2="+p2); } } class Person implements Serializable{ // 自动产生一个序列化ID private static final long serialVersionUID = 3517371942205369063L; public String name; public int age; public Address address; public Person() {} public Person(String name, int age) { super(); this.name = name; this.age = age; this.address = new Address(); } //深度拷贝 public Object deepClone() throws Exception{ // 序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 反序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public void setAddress(String provices,String city) { address.setAddress(provices, city); } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", address=" + address + "]"; } } // 这个Person内部的Address对象也要实现序列化接口,否则Person序列化的时候会报错 class Address implements Serializable{ // 自动产生一个序列化ID private static final long serialVersionUID = 693166268942097920L; private String provices; private String city; public void setAddress(String provices,String city) { this.provices = provices; this.city = city; } @Override public String toString() { return "Address [provices=" + provices + ", city=" + city + "]"; } public String getProvices() { return provices; } public void setProvices(String provices) { this.provices = provices; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }