it-swarm.com.ru

clone () против конструктора копирования против метода фабрики?

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

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

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

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

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

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

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

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

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

У фабричных методов нет стандартных имен.

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

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 () дополнительные усилия по созданию исключений в не клонируемых подклассах кажутся мне тривиальными.

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

75
User1

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

public class SomeClass<T extends Copyable> {


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

interface Copyable {
    Copyable copy();
}

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

49
Yishai

Существует также шаблон 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);
    }
}
25
akarnokd

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

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

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

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

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

8
Peter Lawrey

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

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

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

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

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

public class MyClass implements Copyable<MyClass> {

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

}
5
Alepac

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

  1. Использование метода Object.clone() требует от нас добавления большого количества синтаксиса в наш код, например, реализации интерфейса Cloneable, определения метода clone(), обработки CloneNotSupportedException и, наконец, вызова 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 - Конструктор копирования и клонирование

2
Naresh Joshi

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

0
Mr. Shiny and New 安宇

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

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

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

0
sorrymissjackson

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

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

0
user3996996

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

В теле класса для Производного от суперкласса Base мы имеем

class Derived extends Base {
}

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

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 () конструктору защищенных копий, который принимает Base, и вы передаете ему все конечные члены.

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

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

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

0
Bonaparte