Java: рекомендуемое решение для глубокого клонирования / копирования экземпляра

177

Мне интересно, если есть рекомендуемый способ сделать глубокий клон / копию экземпляра в Java.

Я имею в виду 3 решения, но я могу пропустить некоторые, и я хотел бы узнать ваше мнение

отредактируйте: включите предложение Bohzo и уточните вопрос: это больше о глубоком клонировании, чем мелком клонировании.

Сделай сам:

закодируйте свойства клона вручную после свойств и убедитесь, что изменяемые экземпляры тоже клонированы.
pro:
- контроль над тем, что будет выполнено
-
минусы быстрого выполнения :
- утомительно писать и поддерживать
- склонны к ошибкам (ошибка копирования / вставки, отсутствующее свойство, переназначенное изменяемое свойство)

Используйте отражение:

С помощью ваших собственных инструментов отражения или с помощью внешнего помощника (например, jakarta common-bean) легко написать общий метод копирования, который будет выполнять работу в одну строку.
Pro:
- Легко писать
- Нет
минусов в обслуживании :
- Меньше контроля над тем, что происходит
- Ошибка подвержена изменчивому объекту, если инструмент отражения тоже не клонирует дочерние объекты
- Более медленное выполнение

Используйте клон рамки:

Используйте платформу, которая сделает это за вас, например:
commons-lang SerializationUtils
Библиотека глубокого клонирования Java
Dozer
Kryo

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

Используйте инструментарий байт-кода для записи клона во время выполнения

javassit , BCEL или cglib могут быть использованы для генерации выделенного клонера так же быстро, как написана одна рука. Кто-то знает библиотеку, использующую один из этих инструментов для этой цели?

Что я здесь упустил?
Какой из них вы бы порекомендовали?

Спасибо.

Гийом
источник
1
очевидно библиотека Java Deep Cloning перемещена сюда: code.google.com/p/cloning
Mr_and_Mrs_D

Ответы:

155

Для глубокого клонирования (клонирует всю иерархию объектов):

  • commons-lang SerializationUtils - используя сериализацию - если все классы находятся под вашим контролем, и вы можете принудительно реализовать Serializable.

  • Java Deep Cloning Library - использование отражения - в случаях, когда классы или объекты, которые вы хотите клонировать, находятся вне вашего контроля (сторонняя библиотека) и вы не можете заставить их реализовать Serializable, или в случаях, когда вы не хотите реализовывать Serializable,

Для мелкого клонирования (клонирует только свойства первого уровня):

  • commons-beanutils BeanUtils - в большинстве случаев.

  • Spring BeanUtils - если вы уже используете spring и, следовательно, имеете эту утилиту на пути к классам.

Я намеренно опустил опцию «сделай сам» - вышеприведенные API обеспечивают хороший контроль над тем, что клонировать, а что нет (например, с помощью transientили String[] ignoreProperties), поэтому повторное создание колеса не является предпочтительным.

Bozho
источник
Спасибо Божо, это ценно. И я согласен с вами по поводу варианта DIY! Вы когда-нибудь пробовали сериализацию общин и / или глубокое клонирование? А как насчет перф?
Гийом
да, я использовал все вышеперечисленные опции по вышеуказанным причинам: только у библиотеки клонирования были некоторые проблемы, когда были задействованы прокси-серверы CGLIB, и не хватало какой-то желаемой функциональности, но я думаю, что это нужно исправить сейчас.
Божо
Эй, если моя сущность подключена и у меня есть ленивые вещи, проверяет ли SerializationUtils базу данных на наличие ленивых свойств? Потому что это то, что я хочу, и это не так!
Космин Космин
если у вас есть активный сеанс - да, это так.
Божо
@Bozho Итак, вы имеете в виду, что если все объекты внутри компонента реализуют сериализуемый объект, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) сделает глубокую копию?
хоп
36

В книге Джошуа Блоха есть целая глава, озаглавленная «Пункт 10: Правильно отвергнуть клона» в которой он объясняет, почему переопределение клона по большей части является плохой идеей, потому что спецификация Java для него создает много проблем.

Он предлагает несколько альтернатив:

  • Используйте шаблон фабрики вместо конструктора:

         public static Yum newInstance(Yum yum);
  • Используйте конструктор копирования:

         public Yum(Yum yum);

Все классы коллекции в Java поддерживают конструктор копирования (например, new ArrayList (l);)

LeWoody
источник
1
Согласовано. В моем проекте я определил Copyableинтерфейс, который содержит getCopy()метод. Просто используйте шаблон прототипа вручную.
gpampara
Ну, я спрашивал не о клонируемом интерфейсе, а о том, как выполнить глубокую операцию клонирования / копирования. С помощью конструктора или фабрики вам все равно нужно создать новый экземпляр из исходного кода.
Гийом
@Guillaume Я думаю, что вы должны быть осторожны при использовании слов глубокий клон / копия. Клонирование и копирование в java НЕ означают одно и то же. В спецификации Java есть что сказать по этому поводу ... Я думаю, вам нужна глубокая копия того, что я могу сказать.
LeWoody
Да, спецификация Java точна в том, что такое клон ... Но мы также можем говорить о клоне в более общем смысле ... Например, одна из библиотек, рекомендованных bohzo, называется «Java Deep Cloning Library» ...
Гийом
2
@LWoodyiii этот newInstance()метод и Yumконструктор будут делать глубокое или поверхностное копирование?
Компьютерщик
5

Используйте XStream toXML / fromXML в памяти. Чрезвычайно быстро и уже давно и набирает силу. Объекты не должны быть сериализуемыми, и вы не должны использовать отражение (хотя XStream делает). XStream может различать переменные, которые указывают на один и тот же объект, и не случайно делает две полные копии экземпляра. Много таких деталей вырабатывалось годами. Я использовал это в течение многих лет, и это начало. Это так же просто, как вы можете себе представить.

new XStream().toXML(myObj)

или

new XStream().fromXML(myXML)

Клонировать,

new XStream().fromXML(new XStream().toXML(myObj))

Более кратко:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));
Ranx
источник
3

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

gson, основанный на отражении, будет работать в большинстве случаев, за исключением того, что transientполя не будут скопированы и объекты с круговой ссылкой с причиной StackOverflowError.

public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;
}
public static void main(String[] args)
{
    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);

}
tiboo
источник
2

Смотря как.

Для скорости используйте DIY. Для пуленепробиваемости используйте отражение.

Кстати, сериализация не совпадает с refl, так как некоторые объекты могут предоставлять переопределенные методы сериализации (readObject / writeObject), и они могут содержать ошибки

Йони Ройт
источник
1
отражение не является пуленепробиваемым: оно может привести в некоторой ситуации, когда ваш клонированный объект имеет ссылку на ваш источник ... Если источник изменится, клон тоже изменится!
Гийом
1

Я бы порекомендовал способ DIY, который в сочетании с хорошими методами hashCode () и equals () должен легко проверяться в модульном тесте.

Доминик Санддая
источник
ну, ленивый я очень волнуется при создании такого фиктивного кода. Но это похоже на более мудрый путь ...
Гийом
2
извините, но DIY - это путь ТОЛЬКО, если для вас не подходит другое решение, которое почти никогда не бывает
Божо
1

Я бы предложил переопределить Object.clone (), сначала вызвать super.clone (), а затем вызвать ref = ref.clone () для всех ссылок, которые вы хотите глубоко скопировать. Это более или менее Сделай сам подход, но нужно немного меньше кодирования.

x4u
источник
2
Это одна из многих проблем (сломанного) метода клонирования: в иерархии классов вы всегда должны вызывать super.clone (), о котором легко забыть, поэтому я бы предпочел использовать конструктор копирования.
helpermethod
0

Для глубокого клонирования внедрите Serializable для каждого класса, который вы хотите клонировать следующим образом.

public static class Obj implements Serializable {
    public int a, b;
    public Obj(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

И затем используйте эту функцию:

public static Object deepClone(Object object) {
    try {
        ByteArrayOutputStream baOs = new ByteArrayOutputStream();
        ObjectOutputStream oOs = new ObjectOutputStream(baOs);
        oOs.writeObject(object);
        ByteArrayInputStream baIs = new ByteArrayInputStream(baOs.toByteArray());
        ObjectInputStream oIs = new ObjectInputStream(baIs);
        return oIs.readObject();
    }
    catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

как это: Obj newObject = (Obj)deepClone(oldObject);

Александр Маслью
источник