This article will discuss the following 4 issues
1. Java Cloneable interface implements deep copy
2. Java serialization implements deep copy
3. The fastest deep copy binary library cloning source code analysis
4. Comparison of speeds of several copy methods
I won’t talk about the concept of deep copy in this article. Implementing deep copy in C++. Generally, overload the assignment operator "=" to implement deep copy between objects of the same class. Therefore, it is natural that in Java we can also define a copy function to assign each property of the object within the function. This method is simple and natural, but there is a fatal problem: if one day a new attribute that requires deep copy is added to the class, the corresponding copy function must also be modified. This method brings great inconvenience to the class's extensibility. How to solve this problem, let’s look at the implementation method of chapters 1, 2, and 3 and the speed test of section 4.
1. The Java Cloneable interface implements deep copy <br />In this way, the class needs to implement the Colneable interface clone function, and call super.clone in the clone function. This deep copy of the method also brings another problem. If there are objects of other classes as properties in the class, other classes also need to be overloaded and implemented in the Cloneable interface. Here is an example. In the following example, ComplexDO contains SimpleDO objects. To implement ComplexDO deep copy, you need to first implement SimpleDO's clone interface:
public class SimpleDO implements Cloneable, Serializable { private int x = 1; private String s = "simpleDO"; @Override protected Object clone() throws CloneNotSupportedException { SimpleDO newClass = (SimpleDO)super.clone(); return newClass; } } public class ComplexDO implements Cloneable, Serializable { private int x = 1; private String s = "complex"; private Integer a = 123; private Integer b = 1234; private Integer c = 1334455; private String s2 = "hehehe"; private String s3 = "hahaha"; private Long id = 1233245L; private ArrayList<SimpleDO> l = new ArrayList<SimpleDO>(); @Override public Object clone() throws CloneNotSupportedException { ComplexDO newClass = (ComplexDO) super.clone(); newClass.l = new ArrayList<SimpleDO>(); for (SimpleDO simple : this.l) { newClass.l.add((SimpleDO) simple.clone()); } return newClass; } } It should be noted that many articles say that the assignment operator of the String type is a deep copy, but in fact, those using assignment operators in Java are shallow copies, but why do articles with such obvious errors have to say that this is a deep copy? My understanding is that String and type attributes are basic types, and the method provided will new objects as long as the internal data changes are designed. Therefore, a String operation will not affect the memory it originally pointed to. Therefore, generally speaking, the assignment operations of basic classes such as String are deep copies.
For this reason, when using String string splicing, new memory needs to be opened, so many people recommend using StringBuilder instead of String for splicing, because StringBuilder only reapplyses larger memory when the built-in char array range is not enough (for modern JVMs, the code will be tuned, and String+String will be optimized into similar instructions for StringBuilder.append). For cropping opposite to splicing, there is a subString function in the String. When using the subString function, is the internal char array of the new String the same as the original String? This is more interesting. If you are interested, you can compare and check the implementation of JDK1.6 and JKD1.7.
2. Java serialization implements deep copy
The principle of this method is to use Java serialization to serialize an object into a binary byte stream, and then deserialize and assign the value to an object. Code example:
public Object seirCopy(Object src) { try { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(src); ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); ObjectInputStream in = new ObjectInputStream(byteIn); Object dest = in.readObject(); return dest; } catch (Exception e) { //do some error handler return null; } } Of course, you can also use json and other serialized libraries to complete serialization. This method effectively avoids the extensible shortcomings of the Cloneabel interface. A function can basically be suitable for all classes. The disadvantage is that it is relative memory copy. Serialization requires first converting the object into a binary byte stream, and then deserializing re-copying the binary byte stream to a piece of object memory, which is relatively slow.
3. The fastest deep copy binary library cloning source code analysis
In the source code, the core processing logic is in the Cloner class.
There are two recursive links:
In (1), fastClone completes objects inherited from the IfastClone interface class, that is, they are all copies of collection operations;
In (2), cloneObject completes the process of obtaining each property of the normal object through the reflection mechanism, and then assigning values to the properties of the newly generated object using Objenesis.
This method is highly extensible. Not only can you rely on its existing code to complete deep copying, but you can also define some cloning methods and types that do not require cloning, which is highly flexible.
4. Comparison of speeds of several copy methods
The above three modes can be used to complete deep copying, and the fastest copying method is what we care about.
First, test the code:
public void testCloneComplex() throws CloneNotSupportedException { final int copyCount = 1; List<ComplexDO> complexDOList = new ArrayList<ComplexDO>(copyCount * 3); final ComplexDO complex = new ComplexDO(); //Calculating the two-side library long start = System.currentTimeMillis(); for(int i = 0; i < copyCount; ++i) { final ComplexDO deepClone = clner.deepClone(complex); complexDOList.add(deepClone); } long end = System.currentTimeMillis(); System.out.println("deepClone cost time=" + (end-start)); //Calling the clone function implemented by the Cloneable interface start = System.currentTimeMillis(); for(int i = 0; i < copyCount; ++i) { final ComplexDO interfaceClone = (ComplexDO) complex.clone(); complexDOList.add(interfaceClone); } end = System.currentTimeMillis(); System.out.println("interfaceClone cost time=" + (end-start)); //Serialization and deserialization generate new object start = System.currentTimeMillis(); for(int i = 0; i < copyCount; ++i) { final ComplexDO seirClone = seirCopy(complex); complexDOList.add(seirClone); } end = System.currentTimeMillis(); System.out.println("seirClone cost time=" + (end-start)); }The unit of run results is milliseconds (this data is ignored and does not calculate java hotspots and possible gcs).
From this table, we can draw the conclusion:
1. The copying of the Cloneable interface is the fastest, because it only involves memory copies, but if the attributes involved are more common objects, it is a bit troublesome to write.
2. Serialization/deserialization copy is the slowest
3. Using the cloning library, the copying of the recursion and reflection mechanism is slower than the Cloneable interface implementation, but faster than the serialization method.
The above is all about this article, I hope it will be helpful to everyone's learning.