Копирование объекта - Object copying
В объектно-ориентированного программирования, копирование объекта является создание копия существующего объект, единица данных в объектно-ориентированном программировании. Полученный объект называется копия объекта или просто копировать оригинального объекта. Копирование является основным, но имеет свои тонкости и может иметь значительные накладные расходы. Есть несколько способов скопировать объект, чаще всего с помощью конструктор копирования или же клонирование. Копирование в основном выполняется для того, чтобы копию можно было изменить или переместить, или сохранить текущее значение. Если что-либо из этого не требуется, ссылки на исходные данные достаточно и более эффективно, поскольку копирование не происходит.
Предметы в универсальном магазине составные данные. В то время как в простых случаях копирование может быть выполнено путем выделения нового неинициализированного объекта и копирования всех полей (атрибуты ) от исходного объекта, в более сложных случаях это не приводит к желаемому поведению.
Способы копирования
Цель дизайна большинства объектов - придать подобие тому, что они сделаны из одного монолитного блока, хотя большинство из них таковыми не являются. Поскольку объекты состоят из нескольких частей, копирование становится нетривиальным. Существует несколько стратегий для лечения этой проблемы.
Рассмотрим объект A, содержащий поля xя (более конкретно, рассмотрим, является ли A строкой и xя представляет собой массив его символов). Существуют разные стратегии создания копии A, называемые мелкая копия и глубокая копия. Многие языки допускают универсальное копирование с помощью одной или любой стратегии, определяя одну копировать операция или отдельный мелкая копия и глубокая копия операции.[1] Обратите внимание, что даже на более мелкую глубину следует использовать ссылка к существующему объекту A, и в этом случае нет нового объекта, только новая ссылка.
Терминология мелкая копия и глубокая копия датируется Болтовня -80.[2] Такое же различие имеет место для сравнения объектов на равенство: в основном существует разница между идентичностью (один и тот же объект) и равенством (одно и то же значение), что соответствует поверхностному равенству и (1 уровень) глубокому равенству двух ссылок на объекты, но затем, кроме того, равенство означает сравнение только полей рассматриваемого объекта или разыменование некоторых или всех полей и сравнение их значений по очереди (например, равны ли два связанных списка, имеют ли они одинаковые узлы или имеют одинаковые значения?).[требуется разъяснение ]
Мелкая копия
Один из методов копирования объекта - это мелкая копия. В этом случае новый объект B созданный, а значения полей A копируются в B.[3][4][5][6] Это также известно как копия по полю,[7][8][9] копия поля в поле, или же копия поля.[10] Если значение поля является ссылкой на объект (например, адрес памяти), оно копирует ссылку, следовательно, ссылаясь на тот же объект, что и A, и если значение поля является примитивным типом, оно копирует значение примитивного типа. В языках без примитивных типов (где все является объектом) все поля копии B являются ссылками на те же объекты, что и поля оригинала A. Таким образом, указанные объекты являются общий, поэтому, если один из этих объектов изменен (из A или B), изменение будет видно в другом. Мелкие копии просты и, как правило, дешевы, поскольку обычно могут быть реализованы простым точным копированием битов.
Глубокая копия
Альтернативой является глубокая копия, означающая, что поля разыменовываются: вместо ссылок на копируемые объекты создаются новые объекты-копии для любых ссылочных объектов, и ссылки на них помещаются в B. Результат отличается от результата, который дает неглубокая копия. в том, что объекты, на которые ссылается копия B, отличаются от объектов, на которые ссылается A, и независимы. Глубокие копии более дороги из-за необходимости создания дополнительных объектов и могут быть значительно более сложными из-за ссылок, которые могут образовывать сложный граф.
Глубокое копирование - это процесс, в котором процесс копирования происходит рекурсивно. Это означает сначала создание нового объекта коллекции, а затем рекурсивное заполнение его копиями дочерних объектов, найденных в оригинале. В случае глубокой копии, копия объекта копируется в другой объект. Это означает, что любые изменения, внесенные в копию объекта, не отражаются в исходном объекте. В Python это реализовано с помощью функции «deepcopy ()».
Комбинация
В более сложных случаях некоторые поля в копии должны иметь общие значения с исходным объектом (как в неглубокой копии), что соответствует «ассоциации»; и у некоторых полей должны быть копии (как в глубокой копии), соответствующие отношениям «агрегация». В этих случаях обычно требуется индивидуальная реализация копирования; эта проблема и решение относятся к Smalltalk-80.[11] В качестве альтернативы поля могут быть помечены как требующие неглубокой или глубокой копии, а операции копирования создаются автоматически (аналогично операциям сравнения).[1] Однако это не реализовано в большинстве объектно-ориентированных языков, хотя в Eiffel есть частичная поддержка.[1]
Выполнение
Почти все объектно-ориентированный языки программирования предоставить способ копирования объектов. Поскольку большинство языков не предоставляют большинство объектов для программ, программист должен определить, как объект должен быть скопирован, точно так же, как они должны определить, являются ли два объекта идентичными или даже сопоставимыми в первую очередь. Многие языки обеспечивают поведение по умолчанию.
То, как решается копирование, зависит от языка к языку и от концепции объекта.
Ленивая копия
Ленивая копия - это реализация глубокой копии. При первоначальном копировании объекта используется (быстрая) неглубокая копия. Счетчик также используется для отслеживания количества объектов, совместно использующих данные. Когда программа хочет изменить объект, она может определить, являются ли данные общими (путем проверки счетчика), и при необходимости может сделать глубокую копию.
Ленивое копирование выглядит снаружи как глубокая копия, но по возможности использует скорость мелкой копии. Обратной стороной являются довольно высокие, но постоянные базовые затраты из-за счетчика. Кроме того, в определенных ситуациях циркулярные ссылки может вызвать проблемы.
Ленивая копия связана с копирование при записи.
В Java
Ниже представлены примеры для одного из наиболее широко используемых объектно-ориентированных языков, Ява, который должен охватывать почти все способы, которыми объектно-ориентированный язык может решить эту проблему.
В отличие от C ++, объекты в Java всегда доступны косвенно через Рекомендации. Объекты никогда не создаются неявно, а всегда передаются или назначаются ссылочной переменной. (Методы в Java всегда пройти по значениюоднако передается значение ссылочной переменной.)[12] В Виртуальная машина Java управляет вывоз мусора так что объекты очищаются после того, как они больше не доступны. В Java нет автоматического способа скопировать любой заданный объект.
Копирование обычно выполняется clone () метод класса. Этот метод обычно, в свою очередь, вызывает метод clone () своего родительского класса для получения копии, а затем выполняет любые пользовательские процедуры копирования. В конце концов это переходит к методу clone () Объект
(самый верхний класс), который создает новый экземпляр того же класса, что и объект, и копирует все поля в новый экземпляр («неглубокая копия»). Если используется этот метод, класс должен реализовывать Клонируемый
маркер интерфейса, иначе он будет бросать исключение CloneNotSupportedException. После получения копии из родительского класса собственный метод clone () класса может затем предоставить возможность настраиваемого клонирования, например глубокое копирование (т. Е. Дублирование некоторых структур, на которые ссылается объект) или присвоение новому экземпляру нового уникального идентификатора.
Тип возврата clone () - Объект
, но разработчики метода клонирования могут вместо этого записать тип клонируемого объекта из-за поддержки Java для ковариантные возвращаемые типы. Одним из преимуществ использования clone () является то, что поскольку это переопределяемый метод, мы можем вызвать clone () для любого объекта, и он будет использовать метод clone () своего класса, при этом вызывающему коду не нужно знать, что это за класс (что может потребоваться с конструктором копирования).
Недостатком является то, что часто невозможно получить доступ к методу clone () для абстрактного типа. Наиболее интерфейсы и абстрактные классы в Java не указывайте общедоступный метод clone (). Таким образом, часто единственный способ использовать метод clone () - это знать класс объекта, что противоречит принципу абстракции использования наиболее универсального типа. Например, если у кого-то есть ссылка на List в Java, нельзя вызвать clone () для этой ссылки, потому что List не определяет общедоступный метод clone (). Реализации List, такие как ArrayList и LinkedList, обычно имеют методы clone (), но переносить тип класса объекта неудобно и плохо.
Другой способ копирования объектов в Java - это сериализовать их через Сериализуемый
интерфейс. Обычно это используется для упорство и проводной протокол целей, но он действительно создает копии объектов, и, в отличие от клона, глубокая копия, которая изящно обрабатывает циклические графы объектов, легко доступна с минимальными усилиями со стороны программиста.
Оба эти метода страдают серьезной проблемой: конструктор не используется для объектов, скопированных с помощью клонирования или сериализации. Это может привести к ошибкам с неправильно инициализированными данными, препятствует использованию окончательный
поля членов и затрудняет обслуживание. Некоторые утилиты пытаются решить эти проблемы с помощью отражения для глубокого копирования объектов, например библиотеки глубокого клонирования.[13]
В Эйфеле
Объекты времени выполнения в Эйфель доступны либо косвенно через Рекомендации или как расширенный объекты, поля которых встроены в объекты, которые их используют. То есть поля объекта хранятся либо внешне или внутренне.
Класс Эйфеля ЛЮБОЙ
содержит функции для мелкого и глубокого копирования и клонирования объектов. Все классы Eiffel наследуются от ЛЮБОЙ
, поэтому эти функции доступны во всех классах и применимы как к ссылочным, так и к развернутым объектам.
В копировать
Функция обеспечивает мелкое копирование от одного объекта к другому по полю. В этом случае новый объект не создается. Если у
были скопированы на Икс
, то те же объекты, на которые ссылается у
перед применением копировать
, также будет ссылаться на Икс
после копировать
функция завершена.
Для создания нового объекта, который является неглубокой копией у
, особенность близнец
используется. В этом случае создается один новый объект с полями, идентичными полям источника.
Особенность близнец
полагается на функцию копировать
, который можно переопределить в потомках ЛЮБОЙ
, если нужно. Результат близнец
якорного типа как текущий
.
Глубокое копирование и создание глубоких двойников можно выполнить с помощью функций deep_copy
и deep_twin
, снова унаследованный от класса ЛЮБОЙ
. Эти функции могут создать множество новых объектов, поскольку они дублируют все объекты во всей структуре объекта. Поскольку вместо простого копирования ссылок на существующие объекты создаются новые повторяющиеся объекты, глубокие операции станут источником проблем с производительностью более легко, чем мелкие операции.
На других языках
В C #, а не с помощью интерфейса ICloneable
, можно использовать общий метод расширения для создания глубокой копии с помощью отражения. У этого есть два преимущества: во-первых, он обеспечивает гибкость для копирования каждого объекта без необходимости указывать каждое свойство и переменную для копирования вручную. Во-вторых, поскольку тип является универсальным, компилятор гарантирует, что целевой объект и исходный объект имеют один и тот же тип.
В Цель-C, методы копировать
и mutableCopy
наследуются всеми объектами и предназначены для выполнения копий; последний предназначен для создания изменяемого типа исходного объекта. Эти методы, в свою очередь, вызывают copyWithZone
и mutableCopyWithZone
методы, соответственно, для выполнения копирования. Объект должен реализовывать соответствующий copyWithZone
копируемый метод.
В OCaml, то библиотека функция Oo.copy выполняет неглубокое копирование объекта.
В Python модуль копирования библиотеки обеспечивает мелкое и глубокое копирование объектов через копия ()
и deepcopy ()
функции соответственно.[14] Программисты могут определять специальные методы __copy __ ()
и __deepcopy __ ()
в объекте, чтобы обеспечить реализацию настраиваемого копирования.
В Рубин, все объекты наследуют два метода выполнения мелких копий, клон и обман. Эти два метода отличаются тем, что клон
копирует испорченное состояние объекта, замороженное состояние и любые одиночка методы, которые он может иметь, тогда как обман
копирует только свое испорченное состояние. Глубокие копии могут быть достигнуты путем сброса и загрузки байтового потока объекта или сериализации YAML.[1] В качестве альтернативы вы можете использовать гем deep_dive для создания контролируемой глубокой копии ваших графов объектов. [2]
В Perl, вложенные структуры хранятся с использованием ссылок, поэтому разработчик может либо перебрать всю структуру и повторно сослаться на данные, либо использовать dclone ()
функция из модуля Хранимый.
В VBA, присвоение переменных типа Объект
- это мелкая копия, присвоение всех других типов (числовые типы, String, определяемые пользователем типы, массивы) - это глубокая копия. Итак, ключевое слово Набор
для присваивания сигнализирует неглубокую копию и (необязательно) ключевое слово Позволять
сигнализирует о глубокой копии. В VBA нет встроенного метода для глубоких копий объектов.
Смотрите также
- Конструктор копирования
- Перегрузка оператора
- Подсчет ссылок
- Копирование при записи
- Клонировать (метод Java)
Примечания
- ^ а б c Грогоно и Саккинен 2000.
- ^ Голдберг и Робсон, 1983 г. С. 97–99. "Есть два способа сделать копии объекта. Разница заключается в том, копируются ли значения переменных объекта. Если значения не копируются, они являются общими (
неглубокий
); если значения копируются, то они не передаются (deepCopy
)." - ^ "Объяснение C ++ Shallow vs Deep Copy".
- ^ "Объяснение .NET Shallow vs Deep Copy".
- ^ "Объяснение Java Shallow vs Deep Copy".
- ^ "Универсальное объяснение мелкой и глубокой копии". Архивировано из оригинал на 2016-03-04. Получено 2013-04-10.
- ^ Ядро Java: основы, том 1, п. 295
- ^ Эффективная Java, Второе издание, п. 54
- ^ "Что это за копирование по полю, сделанное Object.clone ()? ", Переполнение стека
- ^ "Джош Блох о дизайне: беседа с эффективным Java-автором Джошем Блохом", Билл Веннерс, JavaWorld, 4 января 2002 г., п. 13
- ^ Голдберг и Робсон, 1983 г., п. 97. "Реализация по умолчанию
копировать
являетсянеглубокий
. В подклассах, в которых копирование должно приводить к особой комбинации разделяемых и не разделяемых переменных, обычно повторно реализуется метод, связанный с копированием, а не метод, связанный снеглубокий
или жеdeepCopy
." - ^ «Передача информации методу или конструктору». Получено 8 октября 2013.
- ^ Библиотека глубокого клонирования Java
- ^ Модуль копирования Python
Рекомендации
- Гольдберг, Адель; Робсон, Дэвид (1983). Smalltalk-80: язык и его реализация. Пало-Альто, Калифорния: Исследовательский центр Xerox Пало-Альто. ISBN 978-0-201-11371-6.
- Грогоно, Питер; Саккинен, Маркку (12 мая 2000 г.). «Копирование и сравнение: проблемы и решения» (PDF). У Элизы Бертино (ред.). Конспект лекций по информатике. ECOOP 2000 - Объектно-ориентированное программирование. 1850. Springer Berlin Heidelberg. С. 226–250. Дои:10.1007/3-540-45102-1_11. Получено 2015-06-23.