Вопрос-ответ

How to properly override clone method?

Как правильно переопределить метод clone?

Мне нужно реализовать глубокий клон в одном из моих объектов, у которого нет суперкласса.

Каков наилучший способ обработки checked, CloneNotSupportedException выдаваемого суперклассом (который есть Object)?

Коллега посоветовал мне справиться с этим следующим образом:

@Override
public MyObject clone()
{
MyObject foo;
try
{
foo = (MyObject) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error();
}

// Deep clone member fields here

return foo;
}

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

Переведено автоматически
Ответ 1

Вам обязательно использовать clone? Большинство людей согласны с тем, что Java clone сломана.

Джош Блох о дизайне - Копировать конструктор против клонирования


Если вы читали статью о клонировании в моей книге, особенно если вы читаете между строк, вы поймете, что, по моему мнению, clone она глубоко нарушена. [...] Жаль, что Cloneable сломано, но это случается.


Вы можете прочитать больше обсуждений по этой теме в его книге Effective Java 2nd Edition, пункт 11: Переопределять clone разумно. Он рекомендует вместо этого использовать конструктор копирования или фабрику копирования.

Он продолжал писать страницы страниц о том, как, если вы чувствуете, что должны, вы должны реализовать clone. Но он закончил этим:


Все эти сложности действительно необходимы? Редко. Если вы расширяете класс, который реализует Cloneable, у вас нет другого выбора, кроме как реализовать хорошо управляемый clone метод. В противном случае, вам лучше предоставить альтернативные средства копирования объектов или просто не предоставлять такую возможность.


Акцент был сделан им, а не мной.


Поскольку вы ясно дали понять, что у вас нет другого выбора, кроме как реализовать clone, вот что вы можете сделать в этом случае: убедитесь, что MyObject extends java.lang.Object implements java.lang.Cloneable. Если это так, то вы можете гарантировать, что НИКОГДА не поймаете CloneNotSupportedException. Выбрасывание, AssertionError как некоторые предлагали, кажется разумным, но вы также можете добавить комментарий, объясняющий, почему блок catch никогда не будет введен в этом конкретном случае.


В качестве альтернативы, как также предлагали другие, вы, возможно, можете реализовать clone без вызова super.clone.

Ответ 2

Иногда проще реализовать конструктор копирования:

public MyObject (MyObject toClone) {
}

Это избавляет вас от проблем с обработкой CloneNotSupportedException, работает с final полями, и вам не нужно беспокоиться о типе, который нужно возвращать.

Ответ 3

Способ, которым работает ваш код, довольно близок к "каноническому" способу его написания. Я бы добавил AssertionError в catch. Это сигнализирует, что эта строка никогда не должна быть достигнута.

catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
Ответ 4

Есть два случая, в которых CloneNotSupportedException будет выброшен:


  1. Клонируемый класс не реализован Cloneable (предполагается, что фактическое клонирование в конечном итоге откладывается до Objectметода clone). Если класс, в котором вы пишете этот метод, реализует Cloneable, этого никогда не произойдет (поскольку любые подклассы унаследуют его соответствующим образом).

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

Последний случай не может произойти в вашем классе (поскольку вы напрямую вызываете метод суперкласса в try блоке, даже если вызывается из вызывающего подкласса super.clone()), а первый не должен, поскольку ваш класс явно должен реализовывать Cloneable.

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


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

Редактировать: Хотя в целом я должен отметить, что да, clone() действительно сложно реализовать правильно, и вызывающим пользователям сложно понять, будет ли возвращаемое значение тем, что они хотят, вдвойне, если рассматривать глубокие и мелкие клоны. Часто лучше просто полностью избегать всего этого и использовать другой механизм.

java