Should we use clone or BeanUtils.copyProperties and why
Asked Answered
E

8

37

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

Epochmaking answered 21/3, 2013 at 8:24 Comment(0)
S
36

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.

Stupe answered 23/3, 2013 at 19:29 Comment(3)
Nice answer as Josh Bloch's Effective Java notes. Can I summarise that "both can be used depending on senarios"? but both have limits.Hesitate
I think copyProperties() is only calling the setter of the target, it is not "deep" because when it comes to a nested object, the inner field is still a reference, not a copy. Check my answer to see the source code.Finnegan
@Finnegan You think correctly. Anyone who assumes copyProperties makes a "deep" copy in a single call would be mistaken.Stupe
F
11

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 and String 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.

Finnegan answered 1/9, 2019 at 21:21 Comment(0)
E
6

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.

Exorcism answered 24/3, 2013 at 9:51 Comment(1)
Now after spring 4.x It does compare the primitive type of soruce and destination beans . So beware while using BeanUtils copy properties.Keep in mind that you do have a identical primitive types which can be assigned from source to destinationAlimony
O
3

I think you are looking for a deep copy. you can have the below method in a util class and use it for any kind of objct.

public static <T extends Serializable> T copy(T input) {
    ByteArrayOutputStream baos = null;
    ObjectOutputStream oos = null;
    ByteArrayInputStream bis = null;
    ObjectInputStream ois = null;
    try {
        baos = new ByteArrayOutputStream();
        oos = new ObjectOutputStream(baos);
        oos.writeObject(input);
        oos.flush();

        byte[] bytes = baos.toByteArray();
        bis = new ByteArrayInputStream(bytes);
        ois = new ObjectInputStream(bis);
        Object result = ois.readObject();
        return (T) result;
    } catch (IOException e) {
        throw new IllegalArgumentException("Object can't be copied", e);
    } catch (ClassNotFoundException e) {
        throw new IllegalArgumentException("Unable to reconstruct serialized object due to invalid class definition", e);
    } finally {
        closeQuietly(oos);
        closeQuietly(baos);
        closeQuietly(bis);
        closeQuietly(ois);
    }
}
Oliphant answered 28/3, 2013 at 11:21 Comment(0)
O
1

clone creates a shallow copy of the object, the clone object is always of the same class as the original one. All fields, private or not are copied.

BeanUtils.copyProperties API Copy property values from the origin bean to the destination bean for all cases where the property names are the same.

As to me, these two concepts have little in common.

Overlying answered 21/3, 2013 at 8:43 Comment(1)
Hi. I am not questioning your judgement just playing devils advocate. Isnt the purpose of clone to create an object of the same type with the same data? If you use create an empty object (a blank canvas) and copy the properties - are we essentially not doing the same thing?Epochmaking
H
1

From your question I guess that you need deep copy of the object . If this is the case don't use clone method as it is already specified in oracle docs that it provides shallow copy of the associated object. And I don't have enough idea about BeanUtils.copyProperties API.
Given below is the short demo of deep copy. Here I am deep copying a primitive array. You can try this code with any type of object.

import java.io.*;
class ArrayDeepCopy 
{
    ByteArrayOutputStream baos;
    ByteArrayInputStream bins;
    public void saveState(Object obj)throws Exception //saving the stream of bytes of object to `ObjectOutputStream`.
    {
        baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        oos.close();
    }
    public int[][] readState()throws Exception //reading the state back to object using `ObjectInputStream`
    {
        bins = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream oins = new ObjectInputStream(bins);
        Object obj = oins.readObject();
        oins.close();
        return (int[][])obj;
    }
    public static void main(String[] args) throws Exception
    {
        int arr[][]= {
                        {1,2,3},
                        {4,5,7}
                    };
        ArrayDeepCopy ars = new ArrayDeepCopy();
        System.out.println("Saving state...");
        ars.saveState(arr);
        System.out.println("State saved..");
        System.out.println("Retrieving state..");
        int j[][] = ars.readState();
        System.out.println("State retrieved..And the retrieved array is:");
        for (int i =0 ; i < j.length ; i++ )
        {
            for (int k = 0 ; k < j[i].length ; k++)
            {
                System.out.print(j[i][k]+"\t");
            }
            System.out.print("\n");
        }

    }
}
Harmonyharmotome answered 24/3, 2013 at 9:5 Comment(0)
A
0

Cloning is done by you. If the instance which you trying clone contains reference of another instance you have to write cloning code to that one too. What if the instances contains chain of references to other instances? So if you do cloning by yourself, there are chances that you might miss a small detail.

BeanUtils.copyProperties on otherhand take care of everything by itself. It reduces your effort.

Aboveboard answered 28/3, 2013 at 9:7 Comment(0)
C
0

The best solution for a deep copy of Objects of any class would be where there is recursion to handle Object inside an Object as well, and the real GET & SET functions are used to maintain Object Integrity.

The solution I would like to put forward is to find all GET functions of the Source Object and match them with the SET functions of Target Object. If they match in ReturnType -> Parameter then execute the copy. If they are not matching, try to call deep copy for those internal objects.

private void objectCopier(Object SourceObject, Object TargetObject) {
        // Get Class Objects of Source and Target
        Class<?> SourceClass = SourceObject.getClass();
        Class<?> TargetClass = TargetObject.getClass();
        // Get all Methods of Source Class
        Method[] sourceClassMethods = SourceClass.getDeclaredMethods();
        for(Method getter : sourceClassMethods) {
            String getterName = getter.getName();
            // Check if method is Getter
            if(getterName.substring(0,3).equals("get")){
                try {
                    // Call Setter of TargetClass with getter return as parameter
                    TargetClass.getMethod("set"+getterName.substring(3), getter.getReturnType()).invoke(TargetObject, getter.invoke(SourceObject));
                }catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                    try {
                        // Get Class of the type Setter in Target object is expecting
                        Class<?> SetTargetClass = TargetClass.getMethod(getterName).getReturnType();
                        // Create new object of Setter Parameter of Target
                        Object setTargetObject = SetTargetClass.newInstance();
                        // Copy properties of return object of the Source Object to Target Object
                        objectCopier(getter.invoke(SourceObject), setTargetObject);
                        // Set the copied object to the Target Object property
                        TargetClass.getMethod("set"+getterName.substring(3),SetTargetClass).invoke(TargetObject, setTargetObject);
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                            | NoSuchMethodException | SecurityException | InstantiationException ef) {
                        System.out.println(getterName);
                    }
                }
            }
        }
    }
Centrosome answered 11/10, 2021 at 1:11 Comment(2)
Very dangerous. You can find references to the same object and get a nice stack overflow (endless loop).Gabrila
Thank you for your comment @LluisMartinez, Can you please give a little more detail on why it can get the reference to itself?Centrosome

© 2022 - 2024 — McMap. All rights reserved.