Метод clone и интерфейс Cloneable в Java

Метод clone() класса Object

protected native Object clone() throws CloneNotSupportedException;

Интерфейс Cloneable

public interface Cloneable {}

Общий контракт для метода clone

Метод clone создает и возвращает копию этого объекта. Точное значение слова "копия" может зависеть от класса объекта. Общее намерение состоит в том, чтобы для любого объекта x выражение

x.clone() != x

будет true, и выражение

x.clone().getClass() == x.getClass()

будет true, но это не абсолютные требования. Хотя обычно

x.clone().equals(x)

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

  • Если вы переопределите метод clone в нефинальном классе, вы должны вернуть объект, полученный с помощью вызова super.clone()
    • Методы super.clone() автоматически копируют примитивные значения
  • На практике ожидается, что класс, реализующий Cloneable, предоставит правильно работающий метод public clone().
  • метод clone() функционирует как другой конструктор; вы должны убедиться, что он не причиняет вреда исходному объекту и правильно устанавливает инварианты для клона.

Примеры реализации метода clone()

public class Stack {
    private Object[] elements;
    private int size = 0;

    @Override public Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

public class HashTable implements Cloneable {
    private Entry[] buckets = ...;
    private static class Entry {
        final Object key;
        Object value;
        Entry  next;

        Entry(Object key, Object value, Entry next) {
            this.key   = key;
            this.value = value;
            this.next  = next;
        }

        // Рекурсивно копировать связанный список, 
        // возглавляемый этой Entry
        Entry deepCopy() {
            return new Entry(key, value, next == null ? null : next.deepCopy());
        }

        // Итеративное копирование связанного списка, 
        // возглавляемого этой Entry
        Entry deepCopy() {
            Entry result = new Entry(key, value, next);
            for (Entry p = result; p.next != null; p = p.next) {
                p.next = new Entry(p.next.key, p.next.value, p.next.next);
            }
            return result;
        }
    }
    @Override public HashTable clone() {
        try {
            HashTable result = (HashTable) super.clone();
            result.buckets = new Entry[buckets.length];
            for (int i = 0; i < buckets.length; i++) {
                if (buckets[i] != null) {
                    result.buckets[i] = buckets[i].deepCopy();
                }
            }
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

Заключения:

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

Альтернативы использования clone():

  • Конструктор копирования

    public Example(Example example);
    

  • Фабрика копирования

    public static Example newInstance(Example example);
    


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки