Introduction to deep and shallow copies in Java

  • 2020-04-01 03:08:49
  • OfStack

One, the introduction
      An Object Copy is a Copy of an Object's properties into another Object of the same class type. It is common to copy objects in a program, primarily to reuse some or all of the object's data in a new context. There are three types of object Copy in Java: Shallow Copy, Deep Copy, and Lazy Copy.

Two, shallow copy

1. What is shallow copy
    A shallow copy is a bitwise copy of an object, which creates a new object with an exact copy of the original object's property values. If the property is of a primitive type, the value of the primitive type is copied. If the property is a memory address (reference type), the copy is the memory address, so if one of the objects changes that address, the other object is affected.

< img SRC = "/ / files.jb51.net/file_images/article/201403/2014031718073114.png" border = 0 width = 562 height = 303 >

    In the figure, the SourceObject has an int property "field1" and a reference type property "refObj" (an object that references the ContainedObject type). When making a shallow copy of the SourceObject, CopiedObject is created, which has an attribute "field2" with a copy value of "field1" and a reference to refObj itself. Since "field1" is the base type, it simply copies its value to "field2", but since "refObj" is a reference type, CopiedObject points to the same address as "refObj". So any changes to "refObj" in the SourceObject will affect CopiedObject.

2. How to implement shallow copy

Here is an example of implementing shallow copy


public class Subject {
    private String name; 
   public Subject(String s) { 
      name = s; 
   } 
   public String getName() { 
      return name; 
   } 
   public void setName(String s) { 
      name = s; 
   } 
}
public class Student implements Cloneable { 
    //Object reference
   private Subject subj; 

   private String name; 

   public Student(String s, String sub) { 
      name = s; 
      subj = new Subject(sub); 
   } 

   public Subject getSubj() { 
      return subj; 
   } 

   public String getName() { 
      return name; 
   } 

   public void setName(String s) { 
      name = s; 
   } 

    
   public Object clone() { 
      //Shallow copy
      try { 
         //Directly call the clone() method of the parent class
         return super.clone(); 
      } catch (CloneNotSupportedException e) { 
         return null; 
      } 
   } 
}
public class CopyTest {
    public static void main(String[] args) {
        //The original object
        Student stud = new Student("John", "Algebra");
        System.out.println("Original Object: " + stud.getName() + " - " + stud.getSubj().getName());
        //Copy the object
        Student clonedStud = (Student) stud.clone();
        System.out.println("Cloned Object: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
        //The original object Is it the same as the copy object? 
        System.out.println("Is Original Object the same with Cloned Object: " + (stud == clonedStud));
        //The original object And copy the object name Whether the properties are the same 
        System.out.println("Is Original Object's field name the same with Cloned Object: " +
     (stud.getName() == clonedStud.getName()));
        //The original object And copy the object subj Whether the properties are the same 
        System.out.println("Is Original Object's field subj the same with Cloned Object: " +
    (stud.getSubj() == clonedStud.getSubj()));
        stud.setName("Dan");
        stud.getSubj().setName("Physics");
        System.out.println("Original Object after it is updated: " + stud.getName() + " - " +
     stud.getSubj().getName());
        System.out.println("Cloned Object after updating original object: " + clonedStud.getName() +
     " - " + clonedStud.getSubj().getName());
    }
}

The output results are as follows:
  Original Object: John - Algebra
  The Cloned Object: John - Algebra
  Is the Original Object the same with the Cloned Object: false
  Is the Original Object 's field name the same with the Cloned Object: true
  Is the Original Object 's field subj the same with the Cloned Object: true
  Original Object after it is updated: dan-physics
  Cloned Object after updating the original Object: John - Physics

          In this example, I had the class Student to copy implement the Clonable interface and override the clone() method of the Object class, then call the super-clone () method inside the method. From the output we can see that the change to the "name" attribute of the original object stud does not affect the copy object clonedStud, but the change to the "name" attribute of the reference object subj affects the copy object clonedStud.

Three, deep copy
1. What is deep copy
    The deep copy copies all the properties and copies the dynamically allocated memory that the properties point to. A deep copy occurs when an object is copied with the object it references. Deep copy is slower and more expensive than shallow copy.

< img SRC = "/ / files.jb51.net/file_images/article/201403/2014031718073115.png" border = 0 width = 552 height = 344 >

In the figure above, the SourceObject has an int property "field1" and a reference type property "refObj1" (an object that references the ContainedObject type). When making a deep copy of the SourceObject, CopiedObject is created, which has an attribute "field2" with a copy value of "field1" and a reference type attribute "refObj2" with a copy value of "refObj1". So any changes to "refObj" in the SourceObject will not affect CopiedObject

2. How to achieve deep copy
    Here is an example of implementing a deep copy. Only minor changes have been made to the shallow copy example, and the Subject and CopyTest classes have not changed.


public class Student implements Cloneable { 
   //Object reference
   private Subject subj; 

   private String name; 

   public Student(String s, String sub) { 
      name = s; 
      subj = new Subject(sub); 
   } 

   public Subject getSubj() { 
      return subj; 
   } 

   public String getName() { 
      return name; 
   } 

   public void setName(String s) { 
      name = s; 
   } 

    
   public Object clone() { 
      //Deep copy, creating a new object of the copied class so that it is independent of the original object
      Student s = new Student(name, subj.getName()); 
      return s; 
   } 
}

The output results are as follows:
  Original Object: John - Algebra
  The Cloned Object: John - Algebra
  Is the Original Object the same with the Cloned Object: false
  Is the Original Object 's field name the same with the Cloned Object: true
  Is the Original Object 's field subj the same with the Cloned Object: false
  Original Object after it is updated: dan-physics
  Cloned Object after updating the original Object: John - Algebra

  It is easy to spot a change in the clone() method. Because it is a deep copy, you need to create an object of the copy class. Because there are object references in the Student class, you need to implement the Cloneable interface in the Student class and override the clone method.

3. Deep copy through serialization
      Deep copy can also be achieved through serialization. What is serialization for? It writes the entire object graph to a persistent store file and reads it back when needed, which means that when you need to read it back you need a copy of the entire object graph. This is what you really need when you make a deep copy of an object. Note that when you make a deep copy through serialization, you must ensure that all classes in the object graph are serializable.


public class ColoredCircle implements Serializable { 
   private int x; 
   private int y; 

   public ColoredCircle(int x, int y) { 
      this.x = x; 
      this.y = y; 
   } 

   public int getX() { 
      return x; 
   } 

   public void setX(int x) { 
      this.x = x; 
   } 

   public int getY() { 
      return y; 
   } 

   public void setY(int y) { 
      this.y = y; 
   } 

   @Override 
   public String toString() { 
      return "x=" + x + ", y=" + y; 
   } 
}
public class DeepCopy {

   public static void main(String[] args) throws IOException { 
      ObjectOutputStream oos = null; 
      ObjectInputStream ois = null; 

      try { 
         //Create the original serializable object
         ColoredCircle c1 = new ColoredCircle(100, 100); 
         System.out.println("Original = " + c1); 

         ColoredCircle c2 = null; 

         //Deep copy through serialization
         ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
         oos = new ObjectOutputStream(bos); 
         //Serialize and pass the object
         oos.writeObject(c1); 
         oos.flush(); 
         ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); 
         ois = new ObjectInputStream(bin); 
         //Return a new object
         c2 = (ColoredCircle) ois.readObject(); 

         //Verify that the contents are the same
         System.out.println("Copied   = " + c2); 
         //Changes the contents of the original object
         c1.setX(200); 
         c1.setY(200); 
         //View each current content
         System.out.println("Original = " + c1); 
         System.out.println("Copied   = " + c2); 
      } catch (Exception e) { 
         System.out.println("Exception in main = " + e); 
      } finally { 
         oos.close(); 
         ois.close(); 
      } 
   } 
}

The output results are as follows:
  The Original = x = 100, y = 100
  Copied = x = 100, y = 100
  The Original = x = 200, y = 200
  Copied = x = 100, y = 100
Here are a few things you need to do:
(1) ensure that all classes in the object graph are serializable
(2) create input and output streams
(3) use this input/output stream to create object input and object output streams
(4) pass the object you want to copy to the object output stream
(5) read the new object from the object input stream and convert it back to the class of the object you sent

      In this example, I create a ColoredCircle object c1 and serialize it (write it to a ByteArrayOutputStream). Then I deserialize the serialized object and save it to c2. I then modified the original object c1. And then as you can see, c1 is different from c2, and any changes you make to c1 don't affect c2.

Note that serialization has its own limitations and problems:
Using this method will not be able to copy transient variables because they cannot be serialized.

      Then there is performance. Creating a socket, serializing an object, transferring it through a socket, and then deserializing it is slow compared to calling methods on existing objects. So there's a world of difference in performance. If performance is critical to your code, it is not recommended. It takes almost 100 times longer to make a deep copy than to implement the Clonable interface.

Delayed copy
      Delayed copy is a combination of shallow and deep copies and is actually rarely used. When an object is initially copied, a faster shallow copy is used, and a counter is used to record how many objects share the data. When the program wants to modify the original object, it determines whether the data is Shared (by checking the counters) and makes deep copies as needed.
      A delayed copy looks like a deep copy from the outside, but it takes advantage of the speed of a shallow copy whenever possible. Deferred copying is used when references in the original object do not change often. The efficiency drop is high due to the presence of counters, but it is a constant level overhead. Also, in some cases, circular references can cause problems.

How to choose
    If the properties of the object are all primitive types, you can use shallow copies, but if the object has reference properties, you can choose between shallow and deep copies based on your specific requirements. What I mean is that if the object reference is not changed at any time, then there is no need to use a deep copy, just a shallow copy. If object references change frequently, use a deep copy. There are no set rules, it all depends on the needs.


Related articles: