Should we use clone or BeanUtils.copyProperties and why

前端 未结 7 1720
醉话见心
醉话见心 2020-12-13 04:47

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 Clon

相关标签:
7条回答
  • 2020-12-13 05:08

    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.

    0 讨论(0)
  • 2020-12-13 05:08

    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.

    0 讨论(0)
  • 2020-12-13 05:14

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-13 05:19

    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.

    0 讨论(0)
  • 2020-12-13 05:22

    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");
            }
    
        }
    }
    
    0 讨论(0)
  • 2020-12-13 05:25

    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 this line:

    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.

    0 讨论(0)
提交回复
热议问题