catalogue
In the process of java development, we often have deep copy requirements, such as copying a request body many times, modifying it into multiple versions, and sending it to different services, such as maintaining different caches. In other cases, there is no need for deep copy, but simple type conversion. For example, to convert a do object into a dto object and return it to the front end. The fields of the two objects are basically the same, but the class name is different. This paper mainly lists the copy methods and suitable scenarios summarized by myself (there are many articles on the principle of deep and shallow copy, which will not be explained in this paper).
Bean definitions used in the copy process:
@Data public class Source { String a; Filed1 filed1; Filed1 filed2; List<Filed1> fileds;
<span class="hljs-meta">@NoArgsConstructor</span> <span class="hljs-meta">@AllArgsConstructor</span> <span class="hljs-meta">@Data</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Filed1</span> </span>{ String id; }
}
Deep copy
1. Manual new
Source source = getSource(); Source target = new Source(); target.setFiled1(new Source.Filed1(source.getFiled1().getId())); target.setFiled2(new Source.Filed1(source.getFiled2().getId()));
<span class="hljs-keyword">if</span> (source.getFileds() != <span class="hljs-keyword">null</span>) { ArrayList<Source.Filed1> fileds = <span class="hljs-keyword">new</span> ArrayList<>(source.getFileds().size()); <span class="hljs-keyword">for</span> (Source.Filed1 filed : source.getFileds()) { fileds.add(<span class="hljs-keyword">new</span> Source.Filed1(filed.getId())); } target.setFileds(fileds); }
Manual new is very simple, but very cumbersome, which is not conducive to later maintenance. Each time you modify the class definition, you need to modify the corresponding copy method, but the performance is very high.
2. clone method
// Source class public Source clone() { Source clone = null; try { clone = (Source) super.clone(); clone.setFiled1(filed1.clone()); clone.setFiled2(filed2.clone()); //Clone of list if (fileds != null) { ArrayList<Filed1> target = new ArrayList<>(this.fileds.size()); for (Filed1 filed : this.fileds) { target.add(filed.clone()); } clone.setFileds(target); } } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } // Filed1 class public Filed1 clone() throws CloneNotSupportedException { return (Filed1) super.clone(); }
When overriding the clone method, if the field type of the class is immutable types such as String and Integer, the field corresponding to the source instance can be reused, thinking that the field value cannot be modified. If the field type is a variable type, it also needs to be rewritten. For example, if the Filed1 field type in the source is not an immutable type, it also needs to rewrite the clone method. In addition, note that the class rewriting the clone method must implement the clonable class (public class Source implements Serializable). Otherwise, CloneNotSupportedException will be thrown.
3. java comes with serialization
ByteArrayOutputStream out = new ByteArrayOutputStream(); new ObjectOutputStream(out).writeObject(source);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); Source target = (Source) in.readObject(); // spring encapsulates the following and can be used directly // Source target = (Source) SerializationUtils.deserialize(SerializationUtils.serialize(source));
This method is mentioned in many books, because serialization to realize deep copy code is relatively simple and has good scalability. Adding fields later does not need to modify the implementation, but the class needs to inherit the tag interface Serializable (public class Source implements Serializable). However, this method has little practical use, because the performance is really very low.
4. json serialization
public class JsonCopy { private static ObjectMapper mapper = new ObjectMapper();
public static String encodeWithoutNull (Object obj) throws Exception { return mapper.writeValueAsString(obj); } public static T decodeValueIgnoreUnknown (String str, Class T clazz) throws Exception { return mapper.readValue(str, clazz); } // Ten million times 15.3 seconds public static T copy(T source, ClassT tClass) throws Exception { return decodeValueIgnoreUnknown(encodeWithoutNull(source), tClass); }
A simple tool class, which uses Jackson library, has average performance, but good scalability, which is much better than java's own serialization.
performance testing
I implemented 10 million copies of the Source object in each method on my own machine and tested the time. The results are as follows:
type | test result |
---|---|
Manual new | Ten million times 774 milliseconds |
clone method | Ten million times 827 milliseconds |
java comes with serialization | Ten million times 109.7 seconds |
json serialization | Ten million times 15.3 seconds |
Deep copy summary
From the perspective of scalability and performance, if you pay attention to performance, use manual new and clone methods; if you pay attention to scalability, use Java's own serialization and json serialization. In normal use, json serialization is preferred, because in most scenarios, cpu is not the bottleneck, and the rewriting clone method is used in some hot code. I think the performance of clone object maintenance method is similar to that of clone object maintenance method, but I think it is similar to that of clone object maintenance method.
Shallow copy
1. spring BeanUtils(Apache BeanUtils)
Source source = getSource(); Source target = new Source(); BeanUtils.copyProperties(source, target);
The principles of spring's BeanuUtils and Apache BeanuUtils are similar. They use reflection to obtain the fields of objects and assign values one by one. In fact, the performance is better. Although reflection is used, the reflection results are internally cached, and the cached results can be directly retrieved later when copying. The performance loss of reflection is in the part of obtaining Class information. When the calling overhead is similar to that of ordinary calls, Jvm will also use Jit for optimization.
2. mapstruct
@Mapper public interface SourceMapper {
SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class); <span class="hljs-function">Source <span class="hljs-title">copy</span><span class="hljs-params">(Source car)</span></span>;
}
Source target = SourceMapper.INSTANCE.copy(source);
The principle types of mapstruct and lombok generate the required methods according to your annotations during compilation, so its performance is theoretically the same as handwriting. Now springboot can also be well combined with it. If you encounter the performance bottleneck of object copy, you can consider using this class library. Unfortunately, he does not support deep copy. https://github.com/mapstruct/mapstruct/issues/695
performance testing
type | test result |
---|---|
BeanUtils | Ten million times 1825 milliseconds |
mapstruct | Ten million times 235 milliseconds |
Shallow copy summary
Shallow copy can also see that instance fields of different objects can be copied. This is an advantage that serialization and Clone methods do not have. It is very useful when converting beans. In general, it is recommended to use Spring's Bean Utils class without introducing additional dependencies, and the performance is sufficient. If you are in a high concurrency scenario, you can consider optimizing through mapstruct, and there will be an order of magnitude gap between the two.