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

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

Каков наилучший способ обработать проверенное CloneNotSupportedException созданное суперclassом (который является 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, чтобы узнать, есть ли другие идеи, которые я могу включить. Благодаря!

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

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

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

    Вы можете прочитать дополнительную дискуссию по этой теме в своей книге « Эффективное Java 2-е издание», пункт 11: разумно переопределить clone . Вместо этого он рекомендует использовать конструктор копирования или фабрику копирования.

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

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

    Акцент был его, а не моего.


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


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

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

     public MyObject (MyObject toClone) { } 

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

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

     catch (CloneNotSupportedException e) { throw new AssertionError(e); } 

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

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

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

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


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

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

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

    Вы можете реализовать защищенные конструкторы копирования следующим образом:

     /* This is a protected copy constructor for exclusive use by .clone() */ protected MyObject(MyObject that) { this.myFirstMember = that.getMyFirstMember(); //To clone primitive data this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects // etc } public MyObject clone() { return new MyObject(this); } 
     public class MyObject implements Cloneable, Serializable{ @Override @SuppressWarnings(value = "unchecked") protected MyObject clone(){ ObjectOutputStream oos = null; ObjectInputStream ois = null; try { ByteArrayOutputStream bOs = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bOs); oos.writeObject(this); ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray())); return (MyObject)ois.readObject(); } catch (Exception e) { //Some seriouse error :< // return null; }finally { if (oos != null) try { oos.close(); } catch (IOException e) { } if (ois != null) try { ois.close(); } catch (IOException e) { } } } } 

    Насколько мне известно большинство ответов, я должен сказать, что ваше решение также является тем, как это делают фактические разработчики Java API. (Или Джош Блох или Нил Гафтер)

    Вот выдержка из openJDK, classа ArrayList:

     public Object clone() { try { ArrayList v = (ArrayList) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } 

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

    Кроме того, вам не нужно переопределять метод, если вы не делаете ничего нового в переопределенном методе. Вам нужно только переопределить его, когда вам нужно выполнить дополнительные операции над объектом или вам нужно сделать его общедоступным.

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

    Просто потому, что реализация Java Cloneable нарушена, это не значит, что вы не можете создать свою собственную.

    Если бы реальная цель OP заключалась в создании глубокого клона, я думаю, что можно создать такой интерфейс:

     public interface Cloneable { public T getClone(); } 

    затем используйте ранее созданный конструктор прототипов для его реализации:

     public class AClass implements Cloneable { private int value; public AClass(int value) { this.vaue = value; } protected AClass(AClass p) { this(p.getValue()); } public int getValue() { return value; } public AClass getClone() { return new AClass(this); } } 

    и еще один class с полем объекта AClass:

     public class BClass implements Cloneable { private int value; private AClass a; public BClass(int value, AClass a) { this.value = value; this.a = a; } protected BClass(BClass p) { this(p.getValue(), p.getA().getClone()); } public int getValue() { return value; } public AClass getA() { return a; } public BClass getClone() { return new BClass(this); } } 

    Таким образом, вы можете легко глубоко клонировать объект classа BClass без необходимости использования @SuppressWarnings или другого трюкового кода.

    Давайте будем гением компьютера.