Подтвердить что ты не робот

Должны ли мы использовать клон или BeanUtils.copyProperties и почему

По внешнему виду - BeanUtils.copyProperties создается клонирование объекта. Если это так, и что с проблемами вокруг реализации интерфейса Cloneable (только неизменяемые объекты новы, где в качестве изменяемых объектов копируются ссылки), что является лучшим и почему?

Вчера я реализовал клонирование, а затем понял, что должен был внести свои собственные изменения для элементов String/Primative. Затем мне сообщили о BeanUtils.copyProperties, который я сейчас использую. Кажется, что обе реализации обеспечивают аналогичную функциональность.

Спасибо

4b9b3361

Ответ 1

Джош Блох приводит несколько довольно хороших аргументов (включая тот, который вы предоставили), утверждая, что Cloneable в корне ошибочен, предпочитая вместо этого конструктор копирования. Смотрите здесь.

Я еще не сталкивался с практическим вариантом использования для копирования неизменяемого объекта. Вы копируете объекты по определенной причине, по-видимому, чтобы изолировать некоторый набор изменяемых объектов в одну транзакцию для обработки, гарантируя, что ничто не сможет изменить их, пока эта единица обработки не будет завершена. Если они уже неизменны, то ссылка так же хороша, как и копия.

BeanUtils.copyProperties часто является менее навязчивым способом копирования без необходимости изменения поддерживаемых классов, и он предлагает некоторую уникальную гибкость при компоновке объектов.

Тем не менее, copyProperties не всегда универсален. В какой-то момент вам может потребоваться поддержка объектов, содержащих типы, которые имеют специализированные конструкторы, но все еще являются изменяемыми. Ваши объекты могут поддерживать внутренние методы или конструкторы для обхода этих исключений, или вы можете зарегистрировать определенные типы в каком-то внешнем инструменте для копирования, но он не может достичь некоторых мест, которые может даже clone(). Это хорошо, но все еще имеет пределы.

Ответ 2

BeanUtils более гибкий, чем стандартный клон, который просто копирует значения полей из объекта в другой. Метод clone копирует поля из beans того же класса, но BeanUtils может сделать это для двух экземпляров разных классов, имеющих одинаковые имена атрибутов.

Например, предположим, что у вас есть Bean A, у которого есть поле String date и Bean B, которые имеют одно и то же поле java.util.Date date. с помощью BeanUtils вы можете скопировать значение строки и автоматически преобразовать его на дату с помощью DateFormat.

Я использовал это для преобразования объекта SOAP в объекты Hibernate, которые не имеют одинаковых типов данных.

Ответ 3

Я думаю, вы ищете глубокую копию. вы можете использовать метод ниже в классе util и использовать его для любого вида объектов.

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);
    }
}

Ответ 4

Из вашего вопроса я предполагаю, что вам нужна глубокая копия объекта. Если это так, не используйте метод clone, поскольку он уже указан в oracle docs, который предоставляет мелкую копию связанного объекта. И мне не хватает представления о BeanUtils.copyProperties API.
Ниже приведена короткая демонстрация deep copy. Здесь я глубоко копирую primitive array. Вы можете попробовать этот код с любым типом объекта.

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");
        }

    }
}

Ответ 5

clone создает мелкую копию объекта, клон-объект всегда имеет тот же класс, что и исходный. Все поля, закрытые или закрытые, копируются.

API BeanUtils.copyProperties Скопируйте значения свойств из источника bean в пункт назначения bean для всех случаев, когда имена свойств совпадают.

Что касается меня, эти две концепции имеют мало общего.

Ответ 6

Клонирование выполняется вами. Если экземпляр, который вы пытаетесь клонировать, содержит ссылку другого экземпляра, вам также нужно написать код клонирования. Что делать, если экземпляры содержат цепочку ссылок на другие экземпляры? Итак, если вы клонируете сами, есть вероятность, что вы можете пропустить небольшую деталь.

BeanUtils.copyProperties по другому заботится обо всем сам по себе. Это уменьшает ваши усилия.

Ответ 7

Я проверил исходный код и обнаружил, что он копирует только "первый уровень" примитивных свойств. Когда речь идет о вложенном объекте, вложенные свойства по-прежнему ссылаются на исходные поля объекта, поэтому это не "глубокая копия".

Проверьте этот фрагмент из исходного кода Spring из org.springframework.beans.BeanUtils.java, версия 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);
                        }
                    }
                }
            }
        }
    }

Просто сфокусируйтесь на этой строке:

writeMethod.invoke(target, value);

Эта строка вызывает установщик целевого объекта. Представьте себе этот класс:

class Student {
    private String name;
    private Address address;
}

Если у нас есть student1 и student2, вторая просто занята и ей не присвоены никакие поля, student1 имеет address1 и имя John.

Итак, если мы позвоним:

    BeanUtils.copyProperties(student1, student2);

Мы делаем:

    student2.setName(student1.getName()); // this is copy because String is immutable
    student2.setAddress(student1.getAddress()); // this is NOT copy, we still are referencing 'address1'

Когда меняется address1, он тоже меняет student2.

Таким образом, BeanUtils.copyProperties() работает только с полями примитивных типов объекта; если он вложенный, он не работает; или вы должны убедиться в неизменности исходного объекта в течение всего жизненного цикла целевого объекта, что нелегко и желательно.


Если вы действительно хотите сделать его глубокой копией, вам нужно реализовать какой-то способ рекурсивного вызова этого метода для полей, которые не являются примитивами. Наконец, вы достигнете класса с только примитивными/неизменными полями, и тогда все готово.