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


176

Мені цікаво, чи є рекомендований спосіб зробити глибокий клон / копію екземпляра в Java.

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

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

Зроби це сам:

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

Використовуйте відображення:

За допомогою власних інструментів рефлексії або із зовнішнім помічником (як, наприклад, якарта звичайна квасоля) легко написати загальний метод копіювання, який виконає роботу в одному рядку.
pro:
- легко писати
- немає
мінусів обслуговування :
- менший контроль того, що відбувається
- помилка, схильна до змінного об'єкта, якщо інструмент відображення теж не клонує суб'єкти
- повільніше виконання

Використовуйте клонну рамку:

Використовуйте рамки , які роблять це для вас, як:
Загально-Ланг SerializationUtils
Java Deep Cloning Library
Бульдозер
Kryo

pro:
- те саме, що і рефлексія
- більше контролю над тим, що саме буде клоновано.
мінуси:
- кожен змінений екземпляр повністю клонований, навіть наприкінці ієрархії
- може бути дуже повільним для виконання

Використовуйте інструменти байт-коду для написання клону під час виконання

javassit , BCEL або cglib можуть бути використані для створення виділеного клонера так швидко, як написано однією рукою. Хтось знає ліб, використовуючи для цього один із цих інструментів?

Що я тут пропустив?
Який би ви порадили?

Дякую.


1
Мабуть, бібліотека глибокого клонування Java переїхала сюди: code.google.com/p/cloning
Mr_and_Mrs_D

Відповіді:


155

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

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

  • Бібліотека глибокого клонування Java - за допомогою рефлексії - у випадках, коли класи або об’єкти, які ви хочете клонувати, не знаходяться під вашим контролем (стороння бібліотека) і ви не можете змусити їх реалізувати Serializable, або у випадках, якщо ви не хочете реалізувати Serializable.

Для дрібного клонування (клонує лише властивості першого рівня):

  • commons-beanutils BeanUtils - у більшості випадків.

  • Spring BeanUtils - якщо ви вже використовуєте spring, а значить, користуєтеся цією утилітою на classpath.

Я навмисно опустив варіант "зробі сам" - API вище забезпечує хороший контроль над тим, що робити, а що не клонувати (наприклад, за допомогою transientабо String[] ignoreProperties), тому винаходити колесо не бажано.


Дякую Божо, це цінно. І я згоден з вами щодо варіанту DIY! Ви коли-небудь пробували серіалізацію та / або глибоку клонування? А як щодо парфумів?
Гійом

так, я використав усі перераховані вище варіанти, з вищезазначених причин:) У бібліотеці, що клонувала, були деякі проблеми, коли були залучені проксі-сервери CGLIB, і пропущено деякий бажаний функціонал, але я думаю, що це слід виправити зараз.
Божо

Привіт, якщо моя Entity додана, і у мене ледачі, чи SerializationUtils перевіряє базу даних на предмет ледачих властивостей? Тому що це те, що я хочу, а це не так!
Cosmin Cosmin

якщо у вас активний сеанс - так, так і є.
Божо

@Bozho Отже, ви маєте на увазі, якщо всі об'єкти в квасолі реалізують серіалізацію, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) зробить глибоку копію?
хоп

36

У книзі Джошуа Блоха є ціла глава під назвою "Пункт 10: Переосмислити клонів розсудливо", в якій він вивчає, чому переважаючий клон здебільшого є поганою ідеєю, оскільки специфікація Java для нього створює багато проблем.

Він пропонує декілька альтернатив:

  • Використовуйте заводський зразок замість конструктора:

         public static Yum newInstance(Yum yum);
  • Використовуйте конструктор копій:

         public Yum(Yum yum);

Усі класи колекції на Java підтримують конструктор копій (наприклад, новий ArrayList (l);)


1
Домовились. У своєму проекті я визначив Copyableінтерфейс, який містить getCopy()метод. Просто використовуйте шаблон прототипу вручну.
gpampara

Ну, я не запитував про інтерфейс, який можна клонувати, а про те, як виконати глибоку операцію з клонування / копіювання. За допомогою конструктора чи фабрики вам все-таки потрібно створити новий екземпляр зі свого джерела.
Гійом

@Guillaume Я думаю, що вам потрібно бути обережними у використанні слів глибокий клон / копія. Клонування та копіювання в java НЕ означають одне і те ж. Спеціалізація Java має більше сказати про це .... Я думаю, ви хочете отримати глибоку копію того, що я можу сказати.
LeWoody

Добре, специфікація Java точна щодо того, що таке клон ... Але ми можемо також говорити про клон у більш загальному значенні ... Наприклад, одна із ліб, рекомендованих bohzo, має назву "Бібліотека глибокого клонування Java" ...
Гійом

2
@LWoodyiii цей newInstance()метод і Yumконструктор зробив би глибоку або дрібну копію?
Geek


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

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

}

2

Залежить.

Для швидкості використовуйте Зроби сам. Для бронезахисту використовуйте відбиття.

До речі, серіалізація не є такою ж, як refl, оскільки деякі об'єкти можуть надавати переосмислені методи серіалізації (readObject / writeObject), і вони можуть бути помилковими


1
відображення не є кулезахисним: це може призвести до ситуації, коли ваш клонований об’єкт має посилання на ваше джерело ... Якщо джерело зміниться, клон теж зміниться!
Гійом

1

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


ну, ледачий мене дуже сильно гримить, коли створює такий макетний код. Але це виглядає як мудріший шлях ...
Гійом

2
вибачте, але DIY не є способом піти тільки якщо ніяке інше рішення не підходить для you..which майже ніколи
Bozho

1

Я б запропонував замінити Object.clone (), спочатку зателефонуйте super.clone (), а потім зателефонуйте ref = ref.clone () на всі посилання, які ви хочете скопіювати. Це більш-менш " Робіть самостійно", але потрібно трохи менше кодування.


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.