Should we use clone or BeanUtils.copyProperties and why
BeanUtils is more flexible than standard clone that simply copies field values from an object to another. The clone method copies the fields from beans of the same class, but BeanUtils can do that for 2 instances of different classes having the same attribute names.
For example let's suppose that you have a Bean A that have a field String date and a bean B that have the same field java.util.Date date. with BeanUtils you can copy the string value and convert it automatically to date using DateFormat.
I've used that to convert a SOAP object into Hibernate objects which do not have the same data types.
I have checked the source code and I found that it is only copying the "first level" of primitive properties. When it comes to a nested object, the nested properties are still referencing the original object's fields, so it is not a "deep copy".
Check this snippets from Spring source code from org.springframework.beans.BeanUtils.java
, version 5.1.3:
/**
* Copy the property values of the given source bean into the target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* <p>This is just a convenience method. For more complex transfer needs,
* consider using a full BeanWrapper.
* @param source the source bean
* @param target the target bean
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}
...
/**
* Copy the property values of the given source bean into the given target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* @param source the source bean
* @param target the target bean
* @param editable the class (or interface) to restrict property setting to
* @param ignoreProperties array of property names to ignore
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
Just focus on these lines:
Object value = readMethod.invoke(source);
...
writeMethod.invoke(target, value);
This line calls the setter on the target object. Imagine this class:
class Student {
private String name;
private Address address;
}
If we have student1
and student2
, the second is just intanciated and not assigned any fields, student1
has address1
and name John
.
So, if we call:
BeanUtils.copyProperties(student1, student2);
We are doing:
student2.setName(student1.getName()); // this is copy because String is immutable
student2.setAddress(student1.getAddress()); // this is NOT copy, we still are referencing `address1`
When address1
changes, it changes student2
too.
So, BeanUtils.copyProperties()
only works on primitive types fields of the object; if it is nested, it does not work; or, you have to make sure the immutability of the original object during the whole lifecycle of the target object, which, is not easy and desirable.
If you really want to make it a deep copy, you have to implement some way to recursively calls this method on fields which are not primitives. At last you will reach a class with only primitive/immutable fields and then you are done.
Josh Bloch provides some fairly good arguments (including the one you provided) asserting that Cloneable
is fundamentally flawed, favoring a copy constructor instead. See here.
I haven't yet encountered a practical use case for copying an immutable object. You're copying objects for a specific reason, presumably to isolate some set of mutable objects into a single transaction for processing, guaranteeing nothing can alter them until that unit of processing is complete. If they're already immutable then a reference is as good as a copy.
BeanUtils.copyProperties
is often a less intrusive way of copying without having to alter your classes to be supported, and it offers some unique flexibility in compositing objects.
That said, copyProperties
is not always one-size-fits-all. You may at some point need to support objects containing types that have specialized constructors, but are still mutable. Your objects can support internal methods or constructors to work around those exceptions, or you can register specific types into some external tool for copying, but it can't reach some places that even clone()
can. It's good, but still has limits.