О клонируемой Java

95

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

Я хотел бы знать следующее:

  1. Cloneableозначает, что мы можем иметь клон или копию объектов, реализовав Cloneableинтерфейс. Каковы преимущества и недостатки этого?
  2. Как происходит рекурсивное клонирование, если объект является составным объектом?
мечтатель
источник
2
Преимущества и недостатки по сравнению с чем?
Galactus
4
Я прочитал, что это означает преимущество класса, который можно клонировать, а не нет. Не уверен, как еще это можно интерпретировать: S
allyourcode

Ответы:

159

Первое, о чем вы должны знать, Cloneable- не используйте его.

CloneableПравильно осуществить клонирование очень сложно , да и усилия того не стоят.

Вместо этого используйте другие параметры, например apache-commons SerializationUtils(deep-clone) или BeanUtils(shallow-clone), или просто используйте конструктор копирования.

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

Божо
источник
1
Я связал слова Блоха (вместо того, чтобы их цитировать)
Божо
3
Обратите внимание, что Блок говорит не использовать Cloneable. Он не говорит, что нельзя использовать клонирование (по крайней мере, я надеюсь, что нет). Есть много способов просто реализовать клонирование, которые намного более эффективны, чем такие классы, как SerializationUtils или BeanUtils, использующие отражение. Смотрите мой пост ниже для примера.
Чарльз
какая альтернатива конструктору копирования при определении интерфейсов? просто добавить метод копирования?
Benez
@benez Я бы сказал, да. начиная с java-8 у вас могут быть staticметоды в интерфейсах, поэтому просто укажите static WhatEverTheInterface copy(WhatEverTheInterface initial)? но мне интересно, что это дает вам, поскольку вы копируете поля из объекта во время клонирования, но интерфейс определяет только методы. хотите объяснить?
Eugene
40

Сам Cloneable, к сожалению, всего лишь интерфейс-маркер, то есть он не определяет метод clone ().

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

Даже если это именно то поведение, которое вы ищете, вам все равно потребуется реализовать собственный метод clone (), чтобы сделать его общедоступным.

При реализации вашего собственного 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, что, если вы не знаете тип объекта для клонирования, как вы узнаете, какой конструктор копирования класса следует вызвать?
Стив Куо
@ Стив: Я не понимаю. Если вы собираетесь клонировать объект, я предполагаю, что вы уже знаете, что это за тип - в конце концов, у вас есть объект, который вы планируете клонировать. И если есть ситуация, когда ваш объект потерял свой конкретный тип в пользу более общего, не могли бы вы оценить его, используя простой «экземпляр» ???
Bane
4
@Bane: Предположим, у вас есть список объектов, все производных от типа A, возможно, с 10 различными типами. Вы не знаете, какой тип каждого объекта. Использование instanceof в этом случае - ОЧЕНЬ плохая идея. Если вы добавите еще один тип, каждый раз, когда вы это сделаете, вам придется добавлять еще один экземпляр теста. А что, если производные классы находятся в другом пакете, к которому вы даже не можете получить доступ? Клонирование - распространенный образец. Да, реализация java плохая, но есть много способов обойти это, которые будут работать нормально. Конструктор копирования не является эквивалентной операцией.
Чарльз
@Charles: Из-за отсутствия подробного примера и недавнего опыта решения подобных проблем мне придется обратиться к Блоху. Пункт №11. Это длинный и трудный для чтения текст, но в основном он говорит: «Избегайте клонирования, когда можете, конструкторы копирования - ваш друг».
Bane
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, вы не получите нужную копию. А что, если А абстрактно? Некоторые люди предположили следующее:

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

В классе C:

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

Я не использую Cloneable, просто использую то же имя функции. Если вам это не нравится, назовите что-нибудь другое.

Чарльз
источник
Я только что увидел это после того, как ответил на ваш комментарий отдельным ответом; Я вижу, куда вы сейчас идете, но две вещи: 1) OP конкретно спросил об использовании Cloneable (а не об общей концепции клонирования) и 2) вы немного расщепляете здесь волосы, пытаясь различить конструктор копирования и общая концепция клонирования. Идея, которую вы здесь передаете, верна, но в ее корне вы просто используете конструктор копирования. ;)
Bane
Хотя я хочу сказать, что я согласен с вашим подходом здесь, а именно с включением A # copyMethod (), вместо того, чтобы заставлять пользователя напрямую вызывать конструктор копирования.
Bane
5

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

B) Клон по умолчанию создает неглубокую копию, и она останется неглубокой копией, если ваша реализация клона не изменит ее. Это может быть сложно, особенно если в вашем классе есть поля final

Божо прав, клон бывает сложно получить правильно. Конструктор / фабрика копирования удовлетворит большинство потребностей.

ILMTitan
источник
0

Какие недостатки у Cloneable?

Клонирование очень опасно, если объект, который вы копируете, имеет композицию. В этом случае вам нужно подумать о возможном побочном эффекте, поскольку клон создает неглубокую копию:

Допустим, у вас есть один объект для обработки манипуляций, связанных с БД. Скажем, у этого объекта есть Connectionобъект как одно из свойств.

Поэтому , когда кто - то создает клон originalObject, объект создается, скажем, cloneObject. Здесь originalObjectи cloneObjectсодержится такая же ссылка на Connectionобъект.

Допустим, originalObjectзакрывает Connectionобъект, так что теперь cloneObjectон не будет работать, потому что connectionобъект был разделен между ними, и он был фактически закрыт originalObject.

Аналогичная проблема может возникнуть, если, скажем, вы хотите клонировать объект, имеющий IOStream в качестве свойства.

Как происходит рекурсивное клонирование, если объект является составным объектом?

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

027
источник
Ваш последний абзац очень запутан. Cloneableне выполняет копию, Object.cloneделает. «Данные из памяти исходного объекта копируются в память объекта-клона» - вот что Object.cloneделает. Чтобы описать глубокое копирование, нужно говорить о памяти объектов, на которые имеются ссылки.
aioobe