it-swarm.com.ru

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

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

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

105
Cuga

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

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

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

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

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

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

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


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


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

117
polygenelubricants

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

public MyObject (MyObject toClone) {
}

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

53
Aaron Digulla

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

catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
}
11
Chris Jester-Young

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

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

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

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


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

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

9
Andrzej Doyle

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

5
Michał Ziober

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

/* 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);
}
3
Dolph
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) {

                }
        }
    }
}
0
Kvant_hv

Тот факт, что Java-реализация Cloneable не работает, не означает, что вы не можете создать свою собственную.

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

public interface Cloneable<T> {
    public T getClone();
}

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

public class AClass implements Cloneable<AClass> {
    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);
    }
}

и другой класс с полем объекта AClass:

public class BClass implements Cloneable<BClass> {
    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);
    }
}

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

0
Francesco Rogo

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

Вот выдержка из openJDK, класс 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 почти нет шансов быть брошенным, если вы объявили, что реализуете интерфейс Cloneable.

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

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

0
Haggra