深拷贝和浅拷贝主要是针对对象的属性是对象(引用类型)
对于引用类型和基本类型看这篇:基本类型和引用类型
拷贝
拷贝,就是赋值。把一个变量赋给另外一个变量,就是把变量的内容进行拷贝。把一个对象的值赋给另外一个对象,就是把一个对象拷贝一份。
基本类没有问题,因为,基本类型赋值时,赋的是数据(所以,不存在深拷贝和浅拷贝的问题)。
如下:
int num1 = 10;int num2 = num1;// 如果要改变num2的值,num1的值不会改变num2 = 11;System.out.println(num1);System.out.println(num2);
输出
1011
引用类型有问题,因为,引用类型赋值时,赋的值地址(就是引用类型变量在内存中保存的内容)。
如下:
int[] ins1 = new int[]{1,2,3};//这就是一个最简单的浅拷贝int[] ins2 = ins1;//如果要改变ins2所引用的数据:ins2[0]=0时,那么ins1[0]的值也是0。//原因就是 ins1和ins2引用了同一块内存区域。ins2[0] = 0;System.out.println(ins1[0]);System.out.println(ins2[0]);
输出:
00
这是最简单的浅拷贝,因为,只是把ins1的地址拷贝的一份给了ins2,并没有把ins1的数据拷贝一份。所以,拷贝的深度不够。
浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
原型对象和复制出来的新对象,他们的引用类型新的属性如果地址值相同则是浅拷贝。
示例代码
public class Test {@SneakyThrowspublic static void main(String[] args) {User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});User user2 = (User) user1.clone();user2.setName("lisi");user2.getBooks()[0] = "斯国一";System.out.println(user1);System.out.println(user2);}}@Dataclass User implements Cloneable{private String name;private String[] books;public User(String name, String[] books) {this.name = name;this.books = books;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}
输出
User(name=zhangsan, books=[斯国一, 水浒, 红楼梦, 西游记])User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])
内存图

user1中有一个对象books,指向了”三国”,”水浒”,”红楼梦”,”西游记”,浅拷贝了一个user2,他包含的books依然指向”三国”,”水浒”,”红楼梦”,”西游记”,此时通过user2去修改了books里的三国为斯国一,那么user1的引用自然也变成了斯国一。
深拷贝
原型对象和复制出来的新对象,他们的引用类型新的属性如果地址值不相同则是深拷贝。
示例代码
public class Test {@SneakyThrowspublic static void main(String[] args) {User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});User user2 = (User) user1.clone();user2.setName("lisi");user2.getBooks()[0] = "斯国一";System.out.println(user1);System.out.println(user2);}}@Dataclass User implements Cloneable{private String name;private String[] books;public User(String name, String[] books) {this.name = name;this.books = books;}@Overrideprotected Object clone() throws CloneNotSupportedException {User user = (User) super.clone();user.books = user.getBooks().clone();return user;}}
输出
User(name=zhangsan, books=[三国, 水浒, 红楼梦, 西游记])User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])
内存图

user1中有一个对象books,指向了”三国”,”水浒”,”红楼梦”,”西游记”,深拷贝了一个user2,同时把他里面的对象books指向也复制了一份”三国”,”水浒”,”红楼梦”,”西游记”,所以user1和user2指向各自的”三国”,”水浒”,”红楼梦”,”西游记”,此时通过user2将books中的三国改成了斯国一,user1中引用的books不变化。
如何实现浅拷贝
1. 通过clone方法
如上面浅拷贝示例所示,只需要给原型对象(也就是被复制的对象)实现一个Cloneable接口(是一个空接口,是一个标记接口),然后调用super.clone方法产生新的对象,就可以了。
2. 通过构造方法实现浅拷贝
public class Test {@SneakyThrowspublic static void main(String[] args) {User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});User user2 = new User(user1);user2.setName("lisi");user2.getBooks()[0] = "斯国一";System.out.println(user1);System.out.println(user2);}}@Dataclass User{private String name;private String[] books;public User(String name, String[] books) {this.name = name;this.books = books;}// 通过构造方法实现浅拷贝public User(User user) {this.name = user.name;this.books = user.books;}}
User(name=zhangsan, books=[斯国一, 水浒, 红楼梦, 西游记])User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])
如何实现深拷贝
1. 通过对对象内的引用类型再次调用clone方法
如上面深拷贝示例代码,通过对对象内的引用类型再次调用clone方法实现深拷贝。
2. 通过序列化反序列化实现深拷贝
拷贝的对象实现序列化接口,然后通过序列化与反序列化实现深拷贝。
public class Test {@SneakyThrowspublic static void main(String[] args) {User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});User user2 = (User) user1.deepClone();user2.setName("lisi");user2.getBooks()[0] = "斯国一";System.out.println(user1);System.out.println(user2);}}@Dataclass User implements Serializable {private String name;private String[] books;public User(String name, String[] books) {this.name = name;this.books = books;}//深度拷贝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();}}
输出
User(name=zhangsan, books=[三国, 水浒, 红楼梦, 西游记])User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])
