Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should we use clone or BeanUtils.copyProperties and why

Tags:

java

clone

spring

By the looks of it - BeanUtils.copyProperties seems to create a clone of an object. If this is the case, and what with the concerns around implementing the Cloneable interface (Only immutable objects are new where as mutable objects have references copied) which is the best and why?

I yesterday implemented cloneable and then realised I had to provide my own modifications for non String/Primative elements. I was then informed about BeanUtils.copyProperties which I am now using. Both implementations seem to provide a similar functionality.

Thanks

like image 625
Biscuit128 Avatar asked Mar 21 '13 08:03

Biscuit128


People also ask

Is it good to use BeanUtils copyProperties?

Copy Bean Properties Copying properties of one object to another object is often tedious and error-prone for developers. BeanUtils class provides a copyProperties method that copies the properties of source object to target object where the property name is same in both objects.

Does clone method create deep copy?

The deep copy of an object will have an exact copy of all the fields of source object like a shallow copy, but unlike sallow copy if the source object has any reference to object as fields, then a replica of the object is created by calling clone method.

What is deep copy of a Java object?

Deep copy/ cloning is the process of creating exactly the independent duplicate objects in the heap memory and manually assigning the values of the second object where values are supposed to be copied is called deep cloning.

How do I copy one bean to another in spring?

Using copyProperties(Object source, Object target, Class<?> editable) This method copies the property values of the given source bean into the given target bean, only setting properties defined in the given "editable" class (or interface).


2 Answers

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.

like image 160
phatfingers Avatar answered Oct 08 '22 19:10

phatfingers


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.

like image 39
WesternGun Avatar answered Oct 08 '22 18:10

WesternGun