深拷贝和浅拷贝主要是针对对象的属性是对象(引用类型)
对于引用类型和基本类型看这篇:基本类型和引用类型

拷贝

拷贝,就是赋值。把一个变量赋给另外一个变量,就是把变量的内容进行拷贝。把一个对象的值赋给另外一个对象,就是把一个对象拷贝一份。
基本类没有问题,因为,基本类型赋值时,赋的是数据(所以,不存在深拷贝和浅拷贝的问题)。
如下:

  1. int num1 = 10;
  2. int num2 = num1;
  3. // 如果要改变num2的值,num1的值不会改变
  4. num2 = 11;
  5. System.out.println(num1);
  6. System.out.println(num2);

输出

  1. 10
  2. 11

引用类型有问题,因为,引用类型赋值时,赋的值地址(就是引用类型变量在内存中保存的内容)。
如下:

  1. int[] ins1 = new int[]{1,2,3};
  2. //这就是一个最简单的浅拷贝
  3. int[] ins2 = ins1;
  4. //如果要改变ins2所引用的数据:ins2[0]=0时,那么ins1[0]的值也是0。
  5. //原因就是 ins1和ins2引用了同一块内存区域。
  6. ins2[0] = 0;
  7. System.out.println(ins1[0]);
  8. System.out.println(ins2[0]);

输出:

  1. 0
  2. 0

这是最简单的浅拷贝,因为,只是把ins1的地址拷贝的一份给了ins2,并没有把ins1的数据拷贝一份。所以,拷贝的深度不够。

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

原型对象和复制出来的新对象,他们的引用类型新的属性如果地址值相同则是浅拷贝。

示例代码

  1. public class Test {
  2. @SneakyThrows
  3. public static void main(String[] args) {
  4. User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
  5. User user2 = (User) user1.clone();
  6. user2.setName("lisi");
  7. user2.getBooks()[0] = "斯国一";
  8. System.out.println(user1);
  9. System.out.println(user2);
  10. }
  11. }
  12. @Data
  13. class User implements Cloneable{
  14. private String name;
  15. private String[] books;
  16. public User(String name, String[] books) {
  17. this.name = name;
  18. this.books = books;
  19. }
  20. @Override
  21. protected Object clone() throws CloneNotSupportedException {
  22. return super.clone();
  23. }
  24. }

输出

  1. User(name=zhangsan, books=[斯国一, 水浒, 红楼梦, 西游记])
  2. User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])

内存图

image.png
user1中有一个对象books,指向了”三国”,”水浒”,”红楼梦”,”西游记”,浅拷贝了一个user2,他包含的books依然指向”三国”,”水浒”,”红楼梦”,”西游记”,此时通过user2去修改了books里的三国为斯国一,那么user1的引用自然也变成了斯国一。

深拷贝

原型对象和复制出来的新对象,他们的引用类型新的属性如果地址值不相同则是深拷贝。

示例代码

  1. public class Test {
  2. @SneakyThrows
  3. public static void main(String[] args) {
  4. User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
  5. User user2 = (User) user1.clone();
  6. user2.setName("lisi");
  7. user2.getBooks()[0] = "斯国一";
  8. System.out.println(user1);
  9. System.out.println(user2);
  10. }
  11. }
  12. @Data
  13. class User implements Cloneable{
  14. private String name;
  15. private String[] books;
  16. public User(String name, String[] books) {
  17. this.name = name;
  18. this.books = books;
  19. }
  20. @Override
  21. protected Object clone() throws CloneNotSupportedException {
  22. User user = (User) super.clone();
  23. user.books = user.getBooks().clone();
  24. return user;
  25. }
  26. }

输出

  1. User(name=zhangsan, books=[三国, 水浒, 红楼梦, 西游记])
  2. User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])

内存图

image.png
user1中有一个对象books,指向了”三国”,”水浒”,”红楼梦”,”西游记”,深拷贝了一个user2,同时把他里面的对象books指向也复制了一份”三国”,”水浒”,”红楼梦”,”西游记”,所以user1和user2指向各自的”三国”,”水浒”,”红楼梦”,”西游记”,此时通过user2将books中的三国改成了斯国一,user1中引用的books不变化。

如何实现浅拷贝

1. 通过clone方法

如上面浅拷贝示例所示,只需要给原型对象(也就是被复制的对象)实现一个Cloneable接口(是一个空接口,是一个标记接口),然后调用super.clone方法产生新的对象,就可以了。

2. 通过构造方法实现浅拷贝

  1. public class Test {
  2. @SneakyThrows
  3. public static void main(String[] args) {
  4. User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
  5. User user2 = new User(user1);
  6. user2.setName("lisi");
  7. user2.getBooks()[0] = "斯国一";
  8. System.out.println(user1);
  9. System.out.println(user2);
  10. }
  11. }
  12. @Data
  13. class User{
  14. private String name;
  15. private String[] books;
  16. public User(String name, String[] books) {
  17. this.name = name;
  18. this.books = books;
  19. }
  20. // 通过构造方法实现浅拷贝
  21. public User(User user) {
  22. this.name = user.name;
  23. this.books = user.books;
  24. }
  25. }
  1. User(name=zhangsan, books=[斯国一, 水浒, 红楼梦, 西游记])
  2. User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])

如何实现深拷贝

1. 通过对对象内的引用类型再次调用clone方法

如上面深拷贝示例代码,通过对对象内的引用类型再次调用clone方法实现深拷贝。

2. 通过序列化反序列化实现深拷贝

拷贝的对象实现序列化接口,然后通过序列化与反序列化实现深拷贝。

  1. public class Test {
  2. @SneakyThrows
  3. public static void main(String[] args) {
  4. User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
  5. User user2 = (User) user1.deepClone();
  6. user2.setName("lisi");
  7. user2.getBooks()[0] = "斯国一";
  8. System.out.println(user1);
  9. System.out.println(user2);
  10. }
  11. }
  12. @Data
  13. class User implements Serializable {
  14. private String name;
  15. private String[] books;
  16. public User(String name, String[] books) {
  17. this.name = name;
  18. this.books = books;
  19. }
  20. //深度拷贝
  21. public Object deepClone() throws Exception{
  22. // 序列化
  23. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  24. ObjectOutputStream oos = new ObjectOutputStream(bos);
  25. oos.writeObject(this);
  26. // 反序列化
  27. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  28. ObjectInputStream ois = new ObjectInputStream(bis);
  29. return ois.readObject();
  30. }
  31. }

输出

  1. User(name=zhangsan, books=[三国, 水浒, 红楼梦, 西游记])
  2. User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])