Java 中的一切都是对象。基础数据类型,诸如int这些基本类型,操作时一般也是采取的值传递方式,所以有时候也称它为值类型;像数组、类Class、枚举EnumInteger包装类等等,就是典型的引用类型

定义两个类:StudentMajor:

image.png

赋值 vs 浅拷贝 vs 深拷贝

对象赋值

  1. Student codeSheep = new Student();
  2. Student codePig = codeSheep;

严格来说,这种不能算是对象拷贝,因为拷贝的仅仅只是引用关系,并没有生成新的实际对象:

浅拷贝

浅拷贝属于对象克隆方式的一种:
image.png
值类型的字段会复制一份,而引用类型的字段拷贝的仅仅是引用地址,而该引用地址指向的实际对象空间其实只有一份。

实现:**

  1. public class Test {
  2. public static void main(String[] args) throws CloneNotSupportedException {
  3. Major m = new Major("计算机科学与技术",666666);
  4. Student student1 = new Student( "CodeSheep", 18, m );
  5. // 由 student1 拷贝得到 student2
  6. Student student2 = (Student) student1.clone();
  7. System.out.println( student1 == student2 );
  8. System.out.println( student1 );
  9. System.out.println( student2 );
  10. System.out.println( "\n" );
  11. // 修改student1的值类型字段
  12. student1.setAge( 35 );
  13. // 修改student1的引用类型字段
  14. m.setMajorName( "电子信息工程" );
  15. m.setMajorId( 888888 );
  16. System.out.println( student1 );
  17. System.out.println( student2 );
  18. }
  19. }

image.png

  • student1==student2打印false,说明clone()方法的确克隆出了一个新对象;
  • 修改值类型字段并不影响克隆出来的新对象,符合预期;
  • 而修改了student1内部的引用对象,克隆对象student2也受到了波及,说明内部还是关联在一起的

深拷贝

深拷贝相较于上面所示的浅拷贝,除了值类型字段会复制一份,引用类型字段所指向的对象,会在内存中也创建一个副本,就像这个样子:
image.png
实现:
需要对更深一层次的引用类Major做改造,让其也实现Cloneable接口并重写clone()方法:

  1. public class Major implements Cloneable {
  2. @Override
  3. protected Object clone() throws CloneNotSupportedException {
  4. return super.clone();
  5. }
  6. // ... 其他省略 ...
  7. }
  8. public class Student implements Cloneable {
  9. @Override
  10. public Object clone() throws CloneNotSupportedException {
  11. Student student = (Student) super.clone();
  12. student.major = (Major) major.clone(); // 重要!!!
  13. return student;
  14. }
  15. // ... 其他省略 ...
  16. }

image.png

利用反序列化实现深拷贝

  1. public class Student implements Serializable {
  2. private String name; // 姓名
  3. private int age; // 年龄
  4. private Major major; // 所学专业
  5. public Student clone() {
  6. try {
  7. // 将对象本身序列化到字节流
  8. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  9. ObjectOutputStream objectOutputStream =
  10. new ObjectOutputStream( byteArrayOutputStream );
  11. objectOutputStream.writeObject( this );
  12. // 再将字节流通过反序列化方式得到对象副本
  13. ObjectInputStream objectInputStream =
  14. new ObjectInputStream( new ByteArrayInputStream( byteArrayOutputStream.toByteArray() ) );
  15. return (Student) objectInputStream.readObject();
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } catch (ClassNotFoundException e) {
  19. e.printStackTrace();
  20. }
  21. return null;
  22. }
  23. // ... 其他省略 ...
  24. }

当然这种情况下要求被引用的子类(比如这里的Major类)也必须是可以序列化的,即实现了Serializable接口: