Про Java, яку можна клонувати


95

Я шукав кілька підручників, що пояснюють Java Cloneable, але не отримав жодних хороших посилань, і Stack Overflow у будь-якому випадку стає більш очевидним вибором.

Я хотів би знати наступне:

  1. Cloneableозначає, що ми можемо мати клон або копію об’єктів, реалізуючи Cloneableінтерфейс. Які переваги та недоліки цього робити?
  2. Як відбувається рекурсивне клонування, якщо об’єкт є складеним об’єктом?

2
Переваги та недоліки порівняно з чим?
Галактус

4
Я читав, що означає перевагу того, що клас є Cloneable проти ні. Не знаю, як інакше це можна трактувати: S
allyourcode

Відповіді:


159

Перше, про що ви повинні знати Cloneable, - це не використовувати.

Дуже важко здійснити клонування Cloneableправильно, а зусиль не варто.

Замість цього використовуйте деякі інші варіанти, такі як apache-commons SerializationUtils(глибокий клон) або BeanUtils(дрібний клон), або просто використовуйте конструктор копіювання.

Подивіться тут на погляди Джоша Блоха щодо клонування з Cloneable, що пояснює численні недоліки підходу. ( Джошуа Блох був співробітником компанії Sun і керував розробкою численних функцій Java.)


1
Я пов’язав слова Блоха (замість того, щоб цитувати їх)
Божо

3
Зверніть увагу, що Block каже, що не слід використовувати Cloneable. Він не каже, що не використовуй клонування (або, принаймні, я сподіваюся, що ні). Є багато способів реалізувати клонування просто набагато ефективніше, ніж класи, такі як SerializationUtils або BeanUtils, які використовують відображення. Для прикладу див. Мій пост нижче.
Чарльз

яка альтернатива конструктору копіювання при визначенні інтерфейсів? просто додати метод копіювання?
Бенец

@benez Я б сказав так. оскільки Java-8 ви можете мати staticметоди в інтерфейсах, так що просто надайте static WhatEverTheInterface copy(WhatEverTheInterface initial)? але мені цікаво, що це дає вам, оскільки ви копіюєте поля з об'єкта під час клонування, але інтерфейс визначає лише методи. пояснювати?
Євген

40

Cloneable сам по собі є лише маркером-інтерфейсом, тобто: він не визначає метод clone ().

Що робить, це зміна поведінки захищеного методу Object.clone (), який викине CloneNotSupportedException для класів, які не реалізують Cloneable, і виконає неглибоку копію членів для класів, які це роблять.

Навіть якщо це поведінка, яку ви шукаєте, вам все одно доведеться застосувати власний метод clone (), щоб зробити його загальнодоступним.

При реалізації власного клону () ідея полягає в тому, щоб почати з об’єкта, створеного за допомогою super.clone (), який гарантовано буде правильним класом, а потім виконати будь-яку додаткову сукупність полів, якщо дрібна копія не те, що ти хочеш. Викликати конструктор з clone () було б проблематично, оскільки це порушило би успадкування у випадку, якщо підклас хоче додати власну додаткову клоновану логіку; якби він викликав super.clone (), то в цьому випадку він отримав би об'єкт неправильного класу.

Цей підхід оминає будь-яку логіку, яка може бути визначена у ваших конструкторах, що потенційно може бути проблематичним.

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

Більшість розробників не використовують Cloneable з цих причин, а просто впроваджують конструктор копій.

Для отримання додаткової інформації та потенційних підводних каменів Cloneable настійно рекомендую книгу Ефективна Java Джошуа Блоха


12
  1. Клонування викликає позалінгвістичний спосіб побудови об’єктів - без конструкторів.
  2. Клонування вимагає від вас якось лікування CloneNotSupportedException - або заважати коду клієнта для його обробки.
  3. Переваги невеликі - просто не потрібно писати конструктор копіювання вручну.

Отже, використовуйте Cloneable розумно. Це не дає вам достатньої вигоди порівняно із зусиллями, які потрібно докласти, щоб зробити все правильно.


Як сказав Божо, не використовуйте Cloneable. Замість цього використовуйте конструктор копій. javapractices.com/topic/TopicAction.do?Id=12
Бейн

@Bane, що, якщо ти не знаєш тип об'єкта для клонування, як ти знаєш, який конструктор копій класу викликати?
Стів Куо,

@ Стів: Я не слідкую. Якщо ви збираєтеся клонувати об’єкт, я припускаю, ви вже знаєте, що це за тип - врешті-решт, у вас є об’єкт, який ви плануєте клонувати. І якщо є ситуація, коли ваш об'єкт втратив свій специфічний тип до більш загального, чи не могли б ви оцінити його, використовуючи простий "екземпляр" ???
Бейн

4
@Bane: Припустимо, у вас є список об'єктів, усі похідні від типу A, можливо, з 10 різними типами. Ви не знаєте, який тип кожного об’єкта. Використання instanceof у цьому випадку ДУЖЕ погана ідея. Якщо ви додаєте інший тип, кожен раз, коли ви це робите, вам доведеться додавати ще один екземпляр тесту. А що, якщо похідні класи знаходяться в іншому пакеті, до якого ви навіть не можете отримати доступ? Клонування є загальноприйнятою схемою. Так, реалізація Java погана, але є безліч способів обійти це, які будуть працювати нормально. Конструктор копіювання не є еквівалентною операцією.
Чарльз

@Charles: За відсутності детального прикладу та відсутності недавнього досвіду вирішення подібних проблем, мені доведеться перейти до Bloch. Пункт No11. Це довго і трохи важко читається, але в основному сказано: "уникайте клонування, коли тільки можете, конструктори копій - ваш друг".
Бейн

7

Клонування - це основна парадигма програмування. Той факт, що Java, можливо, багато в чому це реалізувала погано, зовсім не зменшує необхідність клонування. І легко застосувати клонування, яке буде працювати, але якщо ви хочете, щоб воно працювало, неглибоке, глибоке, змішане, що завгодно. Ви навіть можете використовувати ім’я clone для функції і не реалізовувати Cloneable, якщо хочете.

Припустимо, у мене є класи A, B і C, де B і C є похідними від A. Якщо у мене є список об’єктів типу A, такий:

ArrayList<A> list1;

Тепер цей список може містити об’єкти типу A, B або C. Ви не знаєте, якого типу є об’єкти. Отже, ви не можете скопіювати список таким чином:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Якщо об’єкт насправді типу B або C, ви не отримаєте правильну копію. А що, якщо A абстрактний? Зараз деякі люди пропонують таке:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

Це дуже, дуже погана ідея. Що робити, якщо ви додасте новий похідний тип? Що робити, якщо B або C в іншому пакеті, і у вас немає доступу до них у цьому класі?

Що ви хотіли б зробити, це:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

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

У класі А:

public A clone() {
    return new A(this);
}

У класі B:

@Override
public B clone() {
    return new B(this);
}

У класі С:

@Override
public C clone() {
    return new C(this):
}

Я не реалізую Cloneable, просто використовуючи одне і те ж ім'я функції. Якщо вам це не подобається, назвіть це інакше.


Я щойно побачив це після відповіді на ваш коментар окремою відповіддю; Я бачу, куди ви зараз прямуєте, однак 2 речі: 1) ОП запитав конкретно про використання Cloneable (а не про загальну концепцію клонування), і 2) ви трохи розділяєте волосся тут, намагаючись розрізнити конструктор копіювання і загальна концепція клонування. Ідея, яку ви тут передаєте, є справедливою, але в її корені ви просто використовуєте конструктор копій. ;)
Бейн

Хоча я хочу сказати, що я погоджуюся з вашим підходом тут, з включенням A # copyMethod (), а не змушуючи користувача викликати конструктор копіювання безпосередньо.
Бейн

5

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

Б) Клон за замовчуванням створює неглибоку копію, і він залишатиметься дрібною копією, якщо це не зміниться у вашій реалізації клону. Це може бути важко, особливо якщо ваш клас має заключні поля

Божо має рацію, клона буває важко отримати правильно. Конструктор копіювання / фабрика задовольнить більшість потреб.


0

Які недоліки Cloneable?

Клонування дуже небезпечно, якщо об'єкт, який ви копіюєте, має композицію. Вам слід подумати про нижчий можливий побічний ефект у цьому випадку, оскільки клон створює неглибоку копію:

Скажімо, у вас є один об’єкт для обробки маніпуляцій, пов’язаних з db. Скажімо, цей об’єкт має Connectionоб’єкт як одну з властивостей.

Тому , коли хтось - то створює клон originalObject, об'єкт створюється, скажімо, cloneObject. Тут originalObjectі cloneObjectмістять однакові посилання на Connectionоб'єкт.

Скажімо, originalObjectзакриває Connectionоб'єкт, тому тепер це cloneObjectне буде працювати, оскільки connectionоб'єкт був спільним для них, і він фактично був закритий originalObject.

Подібна проблема може виникнути, якщо скажімо, що ви хочете клонувати об’єкт, який має властивість IOStream.

Як відбувається рекурсивне клонування, якщо об’єкт є складеним об’єктом?

Cloneable виконує неглибоку копію. Сенс полягає в тому, що дані вихідного об'єкта та об'єкта-клону вказуватимуть на одне посилання / пам'ять. навпаки, у випадку глибокої копії, дані з пам'яті оригінального об'єкта копіюються в пам'ять об'єкта-клону.


Ваш останній абзац дуже заплутаний. Cloneableне виконує копію, Object.cloneробить. "Дані з пам'яті оригінального об'єкта копіюються в пам'ять об'єкта-клону" - саме те, що Object.cloneробить. Вам потрібно поговорити про пам’ять об’єктів, на які посилаються, щоб описати глибоке копіювання.
aioobe
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.