Копирование объекта - Object copying

В объектно-ориентированном программировании , объект копирование является создание копии существующего объекта , блок данных в объектно-ориентированном программировании. Результирующий объект называется копией объекта или просто копией исходного объекта. Копирование является основным, но имеет свои тонкости и может иметь значительные накладные расходы. Есть несколько способов скопировать объект, чаще всего с помощью конструктора копирования или клонирования . Копирование выполняется в основном для того, чтобы копию можно было изменить или переместить, или сохранить текущее значение. Если что-либо из этого не требуется, ссылки на исходные данные достаточно и более эффективно, поскольку копирование не происходит.

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

Способы копирования

Цель дизайна большинства объектов - придать подобие тому, что они сделаны из одного монолитного блока, хотя большинство из них таковыми не являются. Поскольку объекты состоят из нескольких частей, копирование становится нетривиальным. Существует несколько стратегий для лечения этой проблемы.

Рассмотрим объект A, который содержит поля x i (более конкретно, рассмотрим, является ли A строкой, а x i - массивом ее символов). Существуют разные стратегии создания копии A, называемые мелкой копией и глубокой копией . Многие языки допускают универсальное копирование с помощью одной или любой из стратегий, определяя либо одну операцию копирования, либо отдельные операции неглубокого и глубокого копирования . Обратите внимание, что еще более поверхностным является использование ссылки на существующий объект A, и в этом случае нет нового объекта, только новая ссылка.

Терминология мелкого и глубокого копирования восходит к Smalltalk -80. Такое же различие справедливо для сравнения объектов на равенство: в основном существует разница между идентичностью (один и тот же объект) и равенством (одно и то же значение), что соответствует поверхностному равенству и (1 уровень) глубокому равенству двух ссылок на объекты, но затем, кроме того, равенство означает сравнение только полей рассматриваемого объекта или разыменование некоторых или всех полей и сравнение их значений по очереди (например, равны ли два связанных списка, если они имеют одинаковые узлы или имеют одинаковые значения?).

Мелкая копия

Один из методов копирования объекта - это неглубокая копия . В этом случае новый объект Б создается , а поля значения A копируется в B. Это также известно как поле за полем копирования , поля для поля-копию или полей экземпляра . Если значение поля является ссылкой на объект (например, адрес памяти), оно копирует ссылку, следовательно, ссылаясь на тот же объект, что и A, и если значение поля является примитивным типом, оно копирует значение примитивного типа. В языках без примитивных типов (где все является объектом) все поля копии B являются ссылками на те же объекты, что и поля оригинала A. Таким образом , указанные объекты являются общими , поэтому, если один из этих объектов изменяется (из A или Б) изменение видно в другом. Мелкие копии просты и, как правило, дешевы, поскольку их обычно можно реализовать простым точным копированием битов.

Глубокая копия

Выполняется глубокая копия.
Выполняется глубокая копия.
Глубокая копия была завершена.
Глубокая копия была завершена.

Альтернативой является глубокая копия, означающая, что поля разыменовываются: вместо ссылок на копируемые объекты создаются новые объекты-копии для любых ссылочных объектов, и ссылки на них помещаются в B. Результат отличается от результата, который дает неглубокая копия. в том, что объекты, на которые ссылается копия B, отличаются от объектов, на которые ссылается A, и независимы. Глубокие копии более дороги из-за необходимости создания дополнительных объектов и могут быть значительно более сложными из-за ссылок, которые могут формировать сложный граф.

Глубокое копирование - это процесс, в котором процесс копирования происходит рекурсивно. Это означает сначала создание нового объекта коллекции, а затем рекурсивное заполнение его копиями дочерних объектов, найденных в оригинале. В случае глубокой копии копия объекта копируется в другой объект. Это означает, что любые изменения, внесенные в копию объекта, не отражаются в исходном объекте. В Python это реализовано с помощью функции «deep copy ()».

Комбинация

В более сложных случаях некоторые поля в копии должны иметь общие значения с исходным объектом (как в неглубокой копии), что соответствует отношению «ассоциации»; а у некоторых полей должны быть копии (как в глубокой копии), соответствующие отношениям «агрегация». В этих случаях обычно требуется индивидуальная реализация копирования; эта проблема и решение относятся к Smalltalk-80. В качестве альтернативы поля могут быть помечены как требующие неглубокой или глубокой копии, а операции копирования создаются автоматически (аналогично операциям сравнения). Однако это не реализовано в большинстве объектно-ориентированных языков, хотя в Eiffel есть частичная поддержка.

Реализация

Почти все объектно-ориентированные языки программирования предоставляют способ копирования объектов. Поскольку большинство языков не предоставляют большинство объектов для программ, программист должен определить, как объект должен быть скопирован, точно так же, как они должны определить, являются ли два объекта идентичными или даже сопоставимыми в первую очередь. Многие языки обеспечивают поведение по умолчанию.

То, как решается копирование, зависит от языка к языку и от концепции объекта.

Ленивая копия

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

Ленивая копия выглядит снаружи как глубокая копия, но по возможности использует скорость неглубокой копии. Обратной стороной являются довольно высокие, но постоянные базовые затраты из-за счетчика. Кроме того, в определенных ситуациях циклические ссылки могут вызвать проблемы.

Ленивое копирование связано с копированием при записи .

В Java

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

В отличие от C ++, к объектам в Java всегда обращаются косвенно через ссылки . Объекты никогда не создаются неявно, а всегда передаются или назначаются ссылочной переменной. (Методы в Java всегда передаются по значению , однако передается значение ссылочной переменной.) Виртуальная машина Java управляет сборкой мусора, чтобы объекты очищались после того, как они перестали быть доступными. В Java нет автоматического способа скопировать любой заданный объект.

Копирование обычно выполняется методом clone () класса. Этот метод обычно, в свою очередь, вызывает метод clone () своего родительского класса для получения копии, а затем выполняет любые пользовательские процедуры копирования. В конце концов, это переходит к методу clone () Object(самого верхнего класса), который создает новый экземпляр того же класса, что и объект, и копирует все поля в новый экземпляр («неглубокая копия»). Если этот метод используется, класс должен реализовывать Cloneableинтерфейс маркеров, или же он будет бросать в CloneNotSupportedException. После получения копии из родительского класса собственный метод clone () класса может затем предоставить возможность настраиваемого клонирования, например глубокое копирование (т. Е. Дублирование некоторых структур, на которые ссылается объект) или присвоение новому экземпляру нового уникального идентификатора.

Тип возвращаемого значения clone () - это Object, но разработчики метода клонирования могут вместо этого записать тип клонируемого объекта из-за поддержки Java для ковариантных возвращаемых типов . Одно из преимуществ использования clone () заключается в том, что, поскольку это переопределяемый метод , мы можем вызвать clone () для любого объекта, и он будет использовать метод clone () своего класса, при этом вызывающий код не должен знать, что это за класс. (что потребуется с конструктором копирования).

Недостатком является то, что часто невозможно получить доступ к методу clone () для абстрактного типа. Большинство интерфейсов и абстрактных классов в Java не определяют общедоступный метод clone (). Таким образом, часто единственный способ использовать метод clone () - это знать класс объекта, что противоречит принципу абстракции использования наиболее универсального типа. Например, если у кого-то есть ссылка на List в Java, нельзя вызвать clone () для этой ссылки, потому что List не определяет общедоступный метод clone (). Реализации List, такие как ArrayList и LinkedList, обычно имеют методы clone (), но переносить тип класса объекта неудобно и плохо.

Другой способ копирования объектов в Java - сериализовать их через Serializableинтерфейс. Обычно это используется для целей сохранения и проводного протокола , но он действительно создает копии объектов и, в отличие от клона, глубокая копия, которая изящно обрабатывает циклические графы объектов, легко доступна с минимальными усилиями со стороны программиста.

Оба эти метода страдают серьезной проблемой: конструктор не используется для объектов, скопированных с помощью клонирования или сериализации. Это может привести к ошибкам с неправильно инициализированными данными, предотвратить использование finalполей-членов и затруднить обслуживание. Некоторые утилиты пытаются решить эти проблемы с помощью отражения для глубокого копирования объектов, например библиотеки глубокого клонирования.

В Эйфеле

Объекты среды выполнения в Eiffel доступны либо косвенно через ссылки, либо как расширенные объекты, поля которых встроены в объекты, которые их используют. То есть поля объекта хранятся либо снаружи, либо внутри .

Класс Eiffel ANYсодержит функции для поверхностного и глубокого копирования и клонирования объектов. Все классы Eiffel наследуются от ANY, поэтому эти функции доступны во всех классах и применимы как к ссылочным, так и к расширенным объектам.

Эта copyфункция производит неглубокое копирование от одного объекта к другому по полю. В этом случае новый объект не создается. Если yбыли скопированы в x, то те же объекты, на которые ссылались yдо применения copy, также будут ссылаться xпосле завершения copyфункции.

Эта yфункция twinиспользуется для создания нового объекта, который является неглубокой копией . В этом случае создается один новый объект, поля которого идентичны полям источника.

Функция twinполагается на функцию copy, которая при необходимости может быть переопределена в потомках ANY. Результат twinимеет привязанный тип like Current.

Глубокое копирование и создание глубоких двойников может быть выполнено с использованием функций deep_copyи deep_twin, опять же, унаследовано от класса ANY. Эти функции могут создавать множество новых объектов, поскольку они дублируют все объекты во всей структуре объекта. Поскольку вместо простого копирования ссылок на существующие объекты создаются новые повторяющиеся объекты, глубокие операции станут источником проблем с производительностью с большей готовностью, чем мелкие операции.

На других языках

В C # вместо использования интерфейса ICloneableможно использовать универсальный метод расширения для создания глубокой копии с использованием отражения. Это дает два преимущества: во-первых, он обеспечивает гибкость для копирования каждого объекта без необходимости указывать каждое свойство и переменную для копирования вручную. Во-вторых, поскольку тип является универсальным, компилятор гарантирует, что целевой объект и исходный объект имеют один и тот же тип.

В Objective-C методы copyи mutableCopyнаследуются всеми объектами и предназначены для выполнения копий; последний предназначен для создания изменяемого типа исходного объекта. Эти методы в свою очередь , называют copyWithZoneи mutableCopyWithZoneметоды, соответственно, для выполнения копирования. Чтобы объект copyWithZoneможно было копировать, он должен реализовывать соответствующий метод.

В OCaml , то библиотека функции Oo.copy выполняет неглубоко копирование объекта.

В Python , модуль копирования библиотечного обеспечивает неполную копию и глубокую копию объектов через copy()и deepcopy()функции, соответственно. Программисты могут определять специальные методы __copy__()и __deepcopy__()в объекте, чтобы обеспечить настраиваемую реализацию копирования.

В Ruby все объекты наследуют два метода для выполнения мелких копий, clone и dup . Эти два метода отличаются тем, что cloneкопируют испорченное состояние объекта, замороженное состояние и любые одноэлементные методы, которые он может иметь, тогда как dupкопирует только испорченное состояние. Глубокое копирование может быть достигнуто путем сброса и загрузки байтового потока объекта или сериализации YAML. [1] В качестве альтернативы, вы можете использовать гем deep_dive для создания контролируемой глубокой копии ваших графов объектов. [2]

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

В VBA присвоение переменных типа Objectявляется поверхностной копией, а назначение для всех других типов (числовые типы, String, определяемые пользователем типы, массивы) - это глубокая копия. Таким образом, ключевое слово Setдля присваивания сигнализирует о неглубокой копии, а ключевое слово (необязательно) Let- о глубокой копии. В VBA нет встроенного метода для глубоких копий объектов.

Смотрите также

Примечания

использованная литература