Копирование объектов
В объектно-ориентированном программировании копирование объекта — это процесс создания и инициализации нового объекта на основе состояния существующего объекта. Различные способы реализации копирования имеют последствия, которые программист должен понимать, чтобы написать компьютерную программу корректную и производительную .
Копирование позволяет использовать и даже изменять возникающее состояние исходного объекта, представленное его внутренним состоянием, не затрагивая исходный объект.
Стратегии
[ редактировать ]Как правило, объект напоминает монолитную концепцию, но имеет внутреннюю структуру, представляющую собой составные данные – дерево состояний. Было разработано несколько стратегий для копирования этого внутреннего состояния на основе потребностей программы и стоимости времени выполнения.
Самые ранние из обсуждавшихся — это поверхностная и глубокая копия — с терминологией, восходящей к Smalltalk -80. [1]
Аналогичное различие справедливо и для сравнения объектов на равенство . Чтобы два объекта были равными, их состояние должно быть одинаковым в значимом смысле. Два объекта можно считать равными, если их поля равны без перехода на подобъекты (поверхностно). Или, может быть, они считаются равными, только если состояние одинаково во всем дереве объектов (в глубине). [ нужны разъяснения ]
Если две переменные содержат одно и то же ссылочное значение, то очевидно, что они относятся к одному и тому же объекту, который даже более конкретен, чем равен.
Справочная копия
[ редактировать ]даже более поверхностное, чем поверхностное копирование, Копирование ссылки является формой копирования объекта. Эта стратегия обычно используется при передаче объекта методу. Ссылка передается по значению — копии ссылочного значения (вероятно, адреса).
Мелкая копия
[ редактировать ]Неглубокое копирование предполагает создание нового неинициализированного объекта B и копирование каждого значения поля из оригинала A. [2] [3] [4] Благодаря этой процедуре ее также называют копированием по полю . [5] [6] [7] копия поля для поля или копия поля . [8] Если значение поля имеет примитивный тип (например, int), значение копируется таким образом, что изменения значения в B не влияют на значение в A.Если значение поля является ссылкой на объект (например, адрес памяти), ссылка копируется, следовательно, ссылается на тот же объект, что и A. Изменение состояния внутреннего объекта влияет на возникающее состояние как A, так и B, поскольку объекты являются общими.В языке без примитивных типов (где все является объектом) все поля копии ссылаются на те же объекты, что и поля оригинала.
Неглубокая копия часто относительно проста в реализации и дешева в вычислительном отношении. Обычно это можно реализовать путем простого копирования непрерывного блока памяти.
Глубокая копия
[ редактировать ]Глубокое копирование предполагает копирование состояния всех подчиненных объектов — рекурсивное разыменование ссылок на объекты на каждом уровне дерева, которое является состоянием исходного объекта, а также создание новых объектов и копирование полей. Модификация исходного или скопированного объекта, включая их внутренние объекты, не влияет на другой объект, поскольку они не имеют общего содержимого.
Гибридный
[ редактировать ]В более сложных случаях некоторые поля в копии должны иметь общие значения с исходным объектом (как в поверхностной копии), что соответствует отношению ассоциации ; и некоторые поля должны иметь копии (как в глубокой копии), соответствующие отношениям агрегации . В этих случаях обычно требуется специальная реализация копирования; эта проблема и ее решение относятся к Smalltalk-80. [9] Альтернативно, поля могут быть помечены как требующие поверхностного или глубокого копирования, а операции копирования будут генерироваться автоматически (аналогично операциям сравнения). [10] Однако это не реализовано в большинстве объектно-ориентированных языков, хотя в Eiffel имеется частичная поддержка. [10]
Ленивое копирование
[ редактировать ]Ленивое копирование, связанное с копированием при записи , представляет собой реализацию глубокого копирования. При первоначальном копировании объекта выполняется относительно быстрое поверхностное копирование. Счетчик также используется для отслеживания того, сколько объектов совместно используют данные. Когда программа хочет изменить объект, она может определить, являются ли данные общими (путем проверки счетчика), и при необходимости выполнить глубокую копию.
Ленивое копирование обеспечивает семантику глубокого копирования, но, когда это возможно, использует преимущества скорости поверхностного копирования. Обратной стороной являются довольно высокие, но постоянные базовые затраты из-за счетчика. Циклические ссылки могут вызвать проблемы.
Примеры
[ редактировать ]Как правило, объектно-ориентированный язык программирования предоставляет возможность копирования объекта. Программист должен определить, как копируется объект, определенный пользователем, так же, как он должен определить, равны ли два объекта, сравнимы и т. д.
Некоторые языки поддерживают одну из поверхностных или глубоких стратегий, определяя либо одну операцию копирования, либо отдельные поверхностные и глубокие операции. [10] Многие языки предусматривают поведение по умолчанию.
Ява
[ редактировать ]В Java доступ к объекту всегда осуществляется косвенно — через ссылку . Объект никогда не создается неявно, а всегда передается или назначается ссылочной переменной.
Параметры передаются по значению, однако передается значение ссылки. [11]
Виртуальная машина Java управляет сборкой мусора , поэтому объекты очищаются после того, как они становятся недоступными.
Язык не предоставляет автоматического способа копирования объекта.
Копирование обычно выполняется методом clone() . Этот метод обычно вызывает метод clone() своего родительского класса для получения копии, а затем выполняет любые пользовательские процедуры копирования. В конце концов, это доходит до метода clone() самого верхнего объекта ( Object
), который создает новый экземпляр того же класса, что и объект, и копирует все поля в новый экземпляр (неглубокая копия). Если используется этот метод, класс должен реализовать Cloneable
интерфейс, иначе он выдаст «Исключение клонирования не поддерживается». После получения копии из родительского класса собственный метод clone() класса может затем предоставить пользовательские возможности клонирования, такие как глубокое копирование (т. е. дублирование некоторых структур, на которые ссылается объект) или присвоение новому экземпляру нового уникального идентификатора.
Тип возвращаемого значения clone(): Object
, но разработчики метода clone могли бы вместо этого записать тип клонируемого объекта из-за поддержки Java ковариантных возвращаемых типов . Одним из преимуществ использования clone() является то, что, поскольку это переопределяемый метод , мы можем вызвать clone() для любого объекта, и он будет использовать метод clone() своего класса, при этом вызывающему коду не нужно знать, что это за класс. (который понадобится для конструктора копирования).
Недостатком является то, что часто невозможно получить доступ к методу clone() для абстрактного типа. Большинство интерфейсов и абстрактных классов в Java не определяют общедоступный метод clone(). Таким образом, часто единственный способ использовать метод clone() — это знать класс объекта, что противоречит принципу абстракции, согласно которому используется наиболее универсальный тип. Например, если у вас есть ссылка на список в Java, вы не можете вызвать clone() для этой ссылки, поскольку List не указывает общедоступный метод clone(). Реализации List, такие как Array List и Linked List, обычно имеют методы clone(), но перенос типа класса объекта неудобен и является плохой абстракцией.
Другой способ копирования объектов в Java — сериализовать их через метод Serializable
интерфейс. Обычно это используется для целей сохранения и проводного протокола , но оно создает копии объектов и, в отличие от клонирования, глубокая копия, которая корректно обрабатывает циклические графики объектов, легко доступна с минимальными усилиями со стороны программиста.
Оба этих метода страдают от заметной проблемы: конструктор не используется для объектов, скопированных с помощью клонирования или сериализации. Это может привести к ошибкам с неправильно инициализированными данными, препятствует использованию final
поля-члены и усложняет обслуживание. Некоторые утилиты пытаются решить эти проблемы, используя отражение для объектов глубокого копирования, например библиотеку глубокого клонирования. [12]
Эйфелева
[ редактировать ]Объекты времени выполнения в Eiffel доступны либо косвенно через ссылки , либо как расширенные объекты, поля которых встроены в объекты, которые их используют. То есть поля объекта хранятся либо снаружи, либо внутри .
Эйфелев класс ANY
содержит функции поверхностного и глубокого копирования и клонирования объектов. Все классы Eiffel наследуются от ANY
, поэтому эти функции доступны во всех классах и применимы как к ссылочным, так и к расширенным объектам.
The copy
Функция выполняет неглубокую копию поля за полем из одного объекта в другой. В этом случае новый объект не создается. Если y
были скопированы в x
, то те же объекты, на которые ссылается y
перед применением copy
, также будет ссылаться на x
после copy
функция завершена.
Чтобы создать новый объект, который является поверхностной копией y
, особенность twin
используется. В этом случае создается один новый объект с полями, идентичными полям источника.
Особенность twin
зависит от функции copy
, который можно переопределить в потомках ANY
, если необходимо. Результат twin
относится к якорному типу like Current
.
Глубокое копирование и создание глубоких двойников можно выполнить с помощью функций deep_copy
и deep_twin
, снова унаследованный от класса ANY
. Эти функции потенциально могут создавать множество новых объектов, поскольку они дублируют все объекты во всей структуре объекта. Поскольку вместо простого копирования ссылок на существующие объекты создаются новые дублирующиеся объекты, глубокие операции с большей вероятностью станут источником проблем с производительностью, чем поверхностные операции.
С#
[ редактировать ]В C# вместо использования интерфейса ICloneable
, универсальный метод расширения можно использовать для создания глубокой копии с использованием отражения. Это имеет два преимущества. Во-первых, это дает возможность копировать каждый объект без необходимости вручную указывать каждое свойство и переменную, которые необходимо скопировать. Во-вторых, поскольку тип является универсальным, компилятор гарантирует, что целевой объект и исходный объект имеют один и тот же тип. [ нужна ссылка ]
Цель-C
[ редактировать ]В Objective-C методы copy
и mutableCopy
наследуются всеми объектами и предназначены для выполнения копий; последний предназначен для создания изменяемого типа исходного объекта. Эти методы, в свою очередь, вызывают copyWithZone
и mutableCopyWithZone
методы соответственно для выполнения копирования. Объект должен реализовывать соответствующий copyWithZone
метод, который можно копировать. [ нужна ссылка ]
OCaml
[ редактировать ]В OCaml функция библиотечная Oo.copy выполняет поверхностное копирование объекта.
Питон
[ редактировать ]В Python модуль копирования библиотеки обеспечивает поверхностное и глубокое копирование объектов через copy()
и deepcopy()
функции соответственно. [13] Программисты могут определять специальные методы __copy__()
и __deepcopy__()
в объекте для обеспечения пользовательской реализации копирования.
Руби
[ редактировать ]В Ruby все объекты наследуют два метода выполнения поверхностного копирования: clone и dup . Эти два метода отличаются тем, что clone
копирует испорченное состояние объекта, замороженное состояние и любые одноэлементные методы, которые он может иметь, тогда как dup
копирует только свое испорченное состояние. Глубокие копии могут быть достигнуты путем дампа и загрузки потока байтов объекта или сериализации YAML. [1] Альтернативно вы можете использовать гем deep_dive для создания контролируемой глубокой копии графов ваших объектов. [2]
Перл
[ редактировать ]В Perl вложенные структуры хранятся с помощью ссылок, поэтому разработчик может либо перебрать всю структуру и повторно ссылаться на данные, либо использовать метод dclone()
функция из модуля Storable .
ВБА
[ редактировать ]В VBA присвоение переменных типа Object
является поверхностной копией, присвоение для всех остальных типов (числовых типов, строк, определяемых пользователем типов, массивов) является глубокой копией. Итак, ключевое слово Set
для назначения сигнализирует о мелкой копии и (необязательном) ключевом слове Let
сигнализирует о глубокой копии. В VBA нет встроенного метода для глубоких копий объектов. [ нужна ссылка ]
См. также
[ редактировать ]- Копировать конструктор
- Перегрузка оператора
- Подсчет ссылок
- Копирование при записи
- Клонировать (метод Java)
Примечания
[ редактировать ]- ^ Голдберг и Робсон 1983 , стр. 97–99. «Существует два способа создания копий объекта. Разница заключается в том, копируются или нет значения переменных объекта. Если значения не копируются, то они являются общими (
shallowCopy
); если значения копируются, они не передаются (deepCopy
)" - ^ «Объяснение C++ Shallow и Deep Copy» .
- ^ «Объяснение .NET Shallow и Deep Copy» .
- ^ «Общее поверхностное и глубокое объяснение текста» . Архивировано из оригинала 4 марта 2016 г. Проверено 10 апреля 2013 г.
- ^ Core Java: Основы, Том 1, стр. 295
- ^ Эффективная Java , второе издание, с. 54
- ^ « Что это за копирование полей с помощью Object.clone()? », Stack Overflow на русском
- ^ «Джош Блох о дизайне: разговор с эффективным автором Java Джошем Блохом», Билл Веннерс, JavaWorld , 4 января 2002 г., стр. 13
- ^ Голдберг и Робсон 1983 , с. 97. «Реализация по умолчанию
copy
являетсяshallowCopy
. В подклассах, в которых копирование должно привести к особой комбинации общих и общих переменных, обычно переопределяется метод, связанный с копированием, а не метод, связанный сshallowCopy
илиdeepCopy
." - ^ Перейти обратно: а б с Грогоно и Саккинен 2000 .
- ^ «Передача информации в метод или конструктор» . Проверено 8 октября 2013 г.
- ^ Библиотека глубокого клонирования Java
- ^ Модуль копирования Python
Ссылки
[ редактировать ]- Гольдберг, Адель ; Робсон, Дэвид (1983). Smalltalk-80: Язык и его реализация . Пало-Альто, Калифорния: Исследовательский центр Xerox Пало-Альто. ISBN 978-0-201-11371-6 .
- Грогоно, Питер; Саккинен, Маркку (12 мая 2000 г.). «Копирование и сравнение: проблемы и решения» (PDF) . В Элизе Бертино (ред.). Конспекты лекций по информатике . ЭКООП 2000 — Объектно-ориентированное программирование. Том. 1850. Шпрингер Берлин Гейдельберг. стр. 226–250. дои : 10.1007/3-540-45102-1_11 . Проверено 23 июня 2015 г.