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

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

90 голосов
/ 02 ноября 2010

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

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

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

Ответы [ 6 ]

150 голосов
/ 02 ноября 2010

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

Очень трудно реализовать клонирование с Cloneable правильно, и усилия не стоят этого.

Вместо этого используйте некоторые другие опции, такие как apache-commons SerializationUtils (глубокий клон) или BeanUtils (мелкий клон), или просто используйтеcopy-constructor.

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

39 голосов
/ 02 ноября 2010

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

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

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

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

Этот подход обходит любую логику, которая может быть определена в ваших конструкторах, хотя это может быть проблематично.

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

Большинство разработчиков не используют Cloneable по этим причинам, а просто реализуют конструктор копирования.

Для получения дополнительной информации и потенциальных подводных камней Cloneable я настоятельно рекомендую книгу Effective Java byДжошуа Блох

11 голосов
/ 02 ноября 2010
  1. Клонирование вызывает экстралингвистический способ конструирования объектов - без конструкторов.
  2. Клонирование требует, чтобы вы как-то обрабатывали исключение CloneNotSupportedException или беспокоили код клиента для его обработки.
  3. Преимущества невелики - вам просто не нужно вручную писать конструктор копирования.

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

7 голосов
/ 16 февраля 2014

Клонирование - это базовая парадигма программирования. Тот факт, что Java, возможно, реализовал его во многих отношениях, вовсе не уменьшает необходимость клонирования. И легко реализовать клонирование, которое будет работать так, как вы хотите, чтобы оно работало, мелкое, глубокое, смешанное, что угодно. Вы даже можете использовать имя клон для функции и не реализовывать 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, просто использую то же имя функции. Если вам это не нравится, назовите это как-нибудь еще.

5 голосов
/ 02 ноября 2010

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

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

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

0 голосов
/ 13 марта 2016

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

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

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

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

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

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

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

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

...