Я быстро погуглил по реализации clone () на Java и нашел: http://www.javapractices.com/topic/TopicAction.do?Id=71
Он имеет следующий комментарий:
конструкторы копирования и статические фабричные методы предоставляют альтернативу клонированию, и их намного проще реализовать.
Все, что я хочу сделать, это сделать глубокую копию. Кажется, что реализация clone () имеет большой смысл, но эта статья с высоким рейтингом Google немного меня пугает.
Вот проблемы, которые я заметил:
Конструкторы копирования не работают с универсальными шаблонами.
Вот какой-то псевдокод, который не компилируется.
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 () дополнительные усилия по созданию исключений в неклонируемых подклассах кажутся мне тривиальными.
Я что-то упускаю? Приветствуются любые идеи.
Ответы:
В основном клон сломан . Ничто не будет работать с дженериками легко. Если у вас есть что-то вроде этого (сокращено, чтобы понять суть):
public class SomeClass<T extends Copyable> { public T copy(T object) { return (T) object.copy(); } } interface Copyable { Copyable copy(); }
Затем с предупреждением компилятора вы можете выполнить свою работу. Поскольку дженерики стираются во время выполнения, то, что делает копию, будет иметь предупреждение компилятора, генерирующее приведение.
В этом случае этого не избежать.. В некоторых случаях этого можно избежать (спасибо, kb304), но не во всех. Рассмотрим случай, когда вам необходимо поддерживать подкласс или неизвестный класс, реализующий интерфейс (например, вы выполняли итерацию по коллекции копируемых объектов, которые не обязательно генерировали тот же класс).источник
Copyable<T>
в ответе kd304 гораздо больше смысла использовать.Также есть паттерн Строитель. См. Подробности в Эффективной 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); } }
источник
В Java нет конструкторов копирования в том же смысле, что и в C ++.
У вас может быть конструктор, который принимает объект того же типа в качестве аргумента, но немногие классы поддерживают это. (меньше числа поддерживающих возможность клонирования)
Для общего клона у меня есть вспомогательный метод, который создает новый экземпляр класса и копирует поля из оригинала (неглубокая копия), используя отражения (на самом деле что-то вроде отражений, но быстрее)
Для глубокой копии простой подход - сериализовать объект и десериализовать его.
Кстати: я предлагаю использовать неизменяемые объекты, тогда вам не нужно их клонировать. ;)
источник
String
например, это неизменяемый объект, и вы можете передавать String без его копирования.Я думаю, что ответ Ишая можно улучшить, чтобы мы не могли получить предупреждение с помощью следующего кода:
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 ... } }
источник
interface Copyable<T extends Copyable>
что ближе всего к типу self, который вы можете кодировать в Java.interface Copyable<T extends Copyable<T>>
, правда ? ;)Ниже приведены некоторые минусы, из-за которых многие разработчики не используют
Object.clone()
Object.clone()
метода требует от нас добавления большого количества синтаксиса в наш код, такого как реализацияCloneable
интерфейса, определениеclone()
метода и дескриптораCloneNotSupportedException
и, наконец, вызовObject.clone()
и приведение его к нашему объекту.Cloneable
В интерфейсе отсутствуетclone()
метод, это интерфейс-маркер и в нем нет никакого метода, и все же нам нужно реализовать его, чтобы сообщить JVM, что мы можем выполнитьclone()
с нашим объектом.Object.clone()
защищен, поэтому мы должны предоставить свой собственныйclone()
и косвенный вызовObject.clone()
из него.Object.clone()
что мы не вызываем конструктор.clone()
метод в дочернем классе, например,Person
тогда все его суперклассы должны определятьclone()
метод в них или унаследовать его от другого родительского класса, иначеsuper.clone()
цепочка завершится ошибкой.Object.clone()
поддерживает только неглубокую копию, поэтому ссылочные поля нашего недавно клонированного объекта по-прежнему будут содержать объекты, которые удерживались в полях исходного объекта. Чтобы преодолеть это, нам необходимо реализоватьclone()
в каждом классе ссылку на наш класс, а затем клонировать их отдельно в нашемclone()
методе, как в примере ниже.Object.clone()
потому что конечные поля можно изменить только с помощью конструкторов. В нашем случае, если мы хотим, чтобы каждыйPerson
объект был уникальным по идентификатору, мы получим дублированный объект, если будем использовать,Object.clone()
потомуObject.clone()
что не будет вызывать конструктор, и последнееid
поле не может быть измененоPerson.clone()
.Конструкторы копирования лучше, чем
Object.clone()
потому, что ониУзнайте больше о клонировании Java - конструктор копирования против клонирования
источник
Обычно 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");
и ты тогда сделал
это вызовет clone () в классе Derived (тот, что выше), это вызовет super.clone () - который может быть реализован или не реализован, но вам рекомендуется вызывать его. Затем реализация передает вывод super.clone () конструктору защищенной копии, который принимает Base, и вы передаете ему любые final члены.
Затем этот конструктор копирования вызывает конструктор копирования суперкласса (если вы знаете, что он есть) и устанавливает финал.
Когда вы вернетесь к методу clone (), вы установите любые не конечные члены.
Проницательные читатели заметят, что если у вас есть копирующий конструктор в Base, он будет вызываться super.clone () - и будет вызываться снова, когда вы вызовете супер-конструктор в защищенном конструкторе, поэтому вы можете вызывать супер копию-конструктор дважды. Надеюсь, если он блокирует ресурсы, он об этом узнает.
источник
Один из шаблонов, который может сработать для вас, - это копирование на уровне компонента. В основном вы используете конструктор без аргументов и вызываете различные сеттеры для предоставления данных. Вы даже можете использовать различные библиотеки свойств bean-компонентов, чтобы относительно легко устанавливать свойства. Это не то же самое, что выполнение clone (), но для многих практических целей это нормально.
источник
Интерфейс Cloneable не работает в том смысле, что он бесполезен, но клонирование работает хорошо и может повысить производительность для больших объектов - 8 полей и более, но тогда он не сможет выполнить анализ побега. поэтому предпочтительно использовать конструктор копирования большую часть времени. Использование клона в массиве быстрее, чем Arrays.copyOf, поскольку длина гарантированно будет одинаковой.
подробнее здесь https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html
источник
Если кто-то не на 100% осознает все причуды
clone()
, то я бы посоветовал держаться от этого подальше. Я бы не сказал такclone()
сломано. Я бы сказал: используйте его только тогда, когда полностью уверены, что это ваш лучший вариант. Конструктор копирования (или фабричный метод, я думаю, это не имеет значения) легко написать (может быть, долго, но легко), он копирует только то, что вы хотите скопировать, и копирует то, как вы хотите, чтобы вещи копировались. Вы можете обрезать его по своему усмотрению.Плюс: легко отлаживать то, что происходит, когда вы вызываете свой конструктор / фабричный метод копирования.
И
clone()
не создает «глубокую» копию вашего объекта из коробки, если вы имеете в виду, чтоCollection
копируются не только ссылки (например, на a ). Но подробнее о глубоком и мелком читайте здесь: глубокая копия, мелкая копия, клонирование.источник
Чего вам не хватает, так это того, что clone создает мелкие копии по умолчанию и по соглашению, и что создание глубоких копий, как правило, невозможно.
Проблема в том, что вы не можете создать глубокие копии циклических графов объектов, не имея возможности отслеживать, какие объекты были посещены. clone () не обеспечивает такого отслеживания (поскольку это должен быть параметр для .clone ()), и поэтому создает только мелкие копии.
Даже если ваш собственный объект вызывает .clone для всех своих членов, он все равно не будет полной копией.
источник
SuperDuperList<T>
или производный от него, то его клонирование должно дать новый экземпляр того же типа, что и тот, который был клонирован; он должен быть отделен от оригинала, но должен ссылаться на те же самыеT
в том же порядке, что и оригинал. С этого момента ничего, что делается с одним списком, не должно влиять на идентичность объектов, хранящихся в другом. Я не знаю ни одного полезного «соглашения», которое требовало бы от универсальной коллекции для демонстрации любого другого поведения.