Clone() против конструктора копирования vs factory?

Я сделал быстрый google по реализации clone() в Java и нашел: http://www.javapractices.com/topic/TopicAction.do?Id=71

Он имеет следующий комментарий:

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

и статические методы factory предоставляют альтернативу клону и намного проще реализовать.

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

Вот те вопросы, которые я заметил:

Конструкторы копирования не работают с Generics.

Вот некоторый псевдокод, который не будет компилироваться.

public class MyClass<T>{
   ..
   public void copyData(T data){
       T copy=new T(data);//This isn't going to work.    
   }
   ..
}

Пример 1: Использование конструктора копирования в общем классе.

Factory методы не имеют стандартных имен.

Очень приятно иметь интерфейс для многоразового кода.

public class MyClass<T>{
    ..
    public void copyData(T data){
        T copy=data.clone();//Throws an exception if the input was not cloneable
    }
    ..
}

Пример 2: Использование clone() в общем классе.

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

Я что-то упустил? Любые идеи будут оценены.

Ответ 1

В принципе, clone нарушен. Ничто не будет работать с дженериками. Если у вас есть что-то вроде этого (сокращенно, чтобы получить точку):

public class SomeClass<T extends Copyable> {


    public T copy(T object) {
        return (T) object.copy();
    }
}

interface Copyable {
    Copyable copy();
}

Затем с предупреждением компилятора вы можете выполнить задание. Поскольку дженерики стираются во время выполнения, что-то, что делает копия, будет содержать в себе предупреждение о компиляторе. В этом случае этого нельзя избежать.. Этого можно избежать в некоторых случаях (спасибо, kb304), но не во всех. Рассмотрим случай, когда вы должны поддерживать подкласс или неизвестный класс, реализующий интерфейс (например, вы выполняли итерацию через набор копий, которые не обязательно генерировали один и тот же класс).

Ответ 2

Существует также шаблон Builder. Подробнее см. "Эффективная Java".

Я не понимаю вашу оценку. В конструкторе копирования вы полностью осведомлены о типе, почему нужно использовать дженерики?

public class C {
   public int value;
   public C() { }
   public C(C other) {
     value = other.value;
   }
}

Недавно был аналогичный вопрос .

public class G<T> {
   public T value;
   public G() { }
   public G(G<? extends T> other) {
     value = other.value;
   }
}

Выполняемый образец:

public class GenTest {
    public interface Copyable<T> {
        T copy();
    }
    public static <T extends Copyable<T>> T copy(T object) {
        return object.copy();
    }
    public static class G<T> implements Copyable<G<T>> {
        public T value;
        public G() {
        }
        public G(G<? extends T> other) {
            value = other.value;
        }
        @Override
        public G<T> copy() {
            return new G<T>(this);
        }
    }
    public static void main(String[] args) {
        G<Integer> g = new G<Integer>();
        g.value = 1;
        G<Integer> f = g.copy();
        g.value = 2;
        G<Integer> h = copy(g);
        g.value = 3;
        System.out.printf("f: %s%n", f.value);
        System.out.printf("g: %s%n", g.value);
        System.out.printf("h: %s%n", h.value);
    }
}

Ответ 3

В Java нет конструкторов копирования в том же смысле, что и С++.

У вас может быть конструктор, который принимает объект того же типа, что и аргумент, но несколько классов поддерживают это. (меньше числа, которое поддерживает клон)

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

Для глубокой копии простой подход заключается в сериализации объекта и де-сериализации его.

BTW: Я предлагаю использовать неизменяемые объекты, тогда вам не нужно будет клонировать их.;)

Ответ 4

Я думаю, что ответ Yishai может быть улучшен, поэтому у нас не может быть предупреждения со следующим кодом:

public class SomeClass<T extends Copyable<T>> {

    public T copy(T object) {
        return object.copy();
    }
}

interface Copyable<T> {
    T copy();
}

Таким образом, класс, который должен реализовать интерфейс для копирования, должен быть таким:

public class MyClass implements Copyable<MyClass> {

    @Override
    public MyClass copy() {
        // copy implementation
        ...
    }

}

Ответ 5

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

  1. Использование Object.clone() требует, чтобы мы добавили много синтаксиса в наш код, например, реализовали интерфейс Cloneable, определили метод clone() и обработали CloneNotSupportedException и, наконец, Object.clone() и Object.clone() его к нашему объекту.
  2. Cloneable интерфейсе отсутствует метод clone(), это маркерный интерфейс, и в нем нет какого-либо метода, и все же нам нужно его реализовать, просто чтобы сообщить JVM, что мы можем выполнить clone() для нашего объекта.
  3. Object.clone() защищен, поэтому мы должны предоставить наш собственный clone() и косвенно вызвать из него Object.clone().
  4. У нас нет никакого контроля над созданием объекта, потому что Object.clone() не вызывает никакого конструктора.
  5. Если мы пишем метод clone() в дочернем классе, например Person то все его суперклассы должны определять в них метод clone() или наследовать его от другого родительского класса, в противном случае цепочка super.clone() потерпит неудачу.
  6. Object.clone() поддерживает только поверхностное копирование, поэтому ссылочные поля нашего вновь клонированного объекта будут по-прежнему содержать объекты, которые содержались в полях нашего исходного объекта. Чтобы преодолеть это, нам нужно реализовать clone() в каждом классе, чьи ссылки хранятся в нашем классе, а затем клонировать их отдельно в нашем методе clone() как показано в следующем примере.
  7. Мы не можем манипулировать конечными полями в Object.clone() потому что конечные поля могут быть изменены только через конструкторы. В нашем случае, если мы хотим, чтобы каждый объект Person был уникальным по идентификатору, мы получим дубликат объекта, если будем использовать Object.clone() потому что Object.clone() не будет вызывать конструктор, и окончательное поле id нельзя изменить из Person.clone().

Конструкторы копирования лучше, чем Object.clone() потому что они

  1. Не заставляйте нас реализовывать какой-либо интерфейс или генерировать какие-либо исключения, но мы можем сделать это, если это необходимо.
  2. Не требует никаких бросков.
  3. Не требует, чтобы мы зависели от неизвестного механизма создания объекта.
  4. Не требуйте, чтобы родительский класс выполнял какой-либо контракт или что-либо реализовывал
  5. Позвольте нам изменить заключительные поля.
  6. Позвольте нам иметь полный контроль над созданием объекта, мы можем написать нашу логику инициализации в нем.

Узнайте больше о клонировании Java - конструктор копирования и клонирование

Ответ 6

Один шаблон, который может работать для вас, - это bean -уровень копирования. В основном вы используете конструктор no-arg и вызываете различные сеттеры для предоставления данных. Вы можете даже использовать различные библиотеки свойств bean, чтобы установить свойства относительно легко. Это не то же самое, что делать clone(), но для многих практических целей это прекрасно.

Ответ 7

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

В классе класса для Derived из суперкласса Base мы имеем

class Derived extends Base {
}

Итак, в самом упрощенном виде вы добавили бы к этому виртуальный конструктор копии с clone(). (В С++ Джоши рекомендует клонировать как конструктор виртуальной копии.)

protected Derived() {
    super();
}

protected Object clone() throws CloneNotSupportedException {
    return new Derived();
}

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

final String name;
Address address;

/// This protected copy constructor - only constructs the object from super-class and
/// sets the final in the object for the derived class.
protected Derived(Base base, String name) {
   super(base);
   this.name = name;
}

protected Object clone() throws CloneNotSupportedException {
    Derived that = new Derived(super.clone(), this.name);
    that.address = (Address) this.address.clone();
}

Теперь, если вы выполнили, вы получили

Base base = (Base) new Derived("name");

а затем вы сделали

Base clone = (Base) base.clone();

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

Затем этот конструктор копирует вызов конструктора копирования суперкласса (если вы знаете, что он есть) и устанавливает финалы.

Когда вы возвращаетесь к методу clone(), вы устанавливаете любые не заключительные элементы.

Астуальные читатели заметят, что если у вас есть экземпляр-конструктор в базе, он будет вызван super.clone() - и снова будет вызван при вызове супер-конструктора в защищенном конструкторе, поэтому вы можете дважды вызывать супер-копировать-конструктор. Надеюсь, что если это блокирующие ресурсы, он это узнает.

Ответ 8

Интерфейс Cloneable сломан, в том смысле, что он бесполезен, но клон работает хорошо и может привести к повышению производительности для больших объектов - 8 полей и более, но тогда он не сможет выполнить анализ утечки. поэтому предпочтительнее использовать конструктор копирования большую часть времени. Использование клонирования в массиве выполняется быстрее, чем Arrays.copyOf, поскольку длина гарантируется одинаковой.

подробнее здесь https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html

Ответ 9

Если кто-то не знает 100% всех причуд clone(), я бы посоветовал держаться подальше от него. Я бы не сказал, что clone() сломан. Я бы сказал: используйте его только тогда, когда вы полностью уверены, что это ваш лучший вариант. Конструктор копирования (или factory метод, который на самом деле не имеет значения, я думаю) легко писать (может быть, длительный, но простой), он копирует только то, что вы хотите скопировать, и копирует так, как вы хотите, чтобы вещи были скопировано. Вы можете подрезать его под свои нужды.

Плюс: легко отлаживать то, что происходит, когда вы вызываете свой конструктор экземпляра / factory.

И clone() не создает "глубокую" копию вашего объекта из коробки, предполагая, что вы имеете в виду, что копируются не только ссылки (например, на Collection). Но читайте больше о глубокой и мелкой здесь: Глубокая копия, мелкая копия, клон

Ответ 10

Что вам не хватает, так это то, что клон создает мелкие копии по умолчанию и соглашение, а создание глубоких копий вообще невозможно.

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

Даже если ваш собственный объект вызывает .clone для всех его членов, он все равно не будет глубокой копией.