Как реализовать глубокое клонирование
Глубокое погружение в клонирование
Прежде чем мы продолжим концепцию клонирования, давайте освежим наши основы концепцией создания объекта. Когда объекты создаются с использованием оператора new, объекты получают выделение памяти в куче.
Создание объекта в куче
В Java в идеале объекты модифицируются только через ссылочную переменную, т.е. копируется только адрес памяти объекта, и, следовательно, любые изменения в исходном объекте будут отражаться в новой переменной.
Glass objGlass1 = новое стекло ();
Стекло objGlass2 = objGlass1;
Здесь, в этом случае любые изменения, которые вы вносите в объект objGlass1, будут отражаться в объекте objGlass2 и наоборот. Это означает, что ‘ objGlass1 == objGlass2 ‘ вернет true, и ссылочные переменные objGlass1 и objGlass2 ссылаются на один и тот же объект. Однако, если вы намереваетесь скопировать объект, в отличие от простого копирования ссылки на объект, вам понадобится клонирование.
Что такое клонирование?
Клонирование — это процесс копирования объекта, то есть создания нового экземпляра путем копирования самого себя. Клонирование в Java может быть выполнено с помощью метода clone () объекта.
Клонирование создает и возвращает копию объекта с тем же классом и со всеми полями, имеющими одинаковые значения.
Glass objGlass1 = новое стекло ();
Glass objGlass2 = (Стекло) objGlass.clone ();
Давайте посмотрим на анализ ниже после клонирования:
Мелкое клонирование против глубокого клонирования
Java поддерживает два типа клонирования — мелкое клонирование и глубокое клонирование.
В случае мелкого клонирования создается новый объект, который имеет точную копию значений в исходном объекте. Метод clone () объекта обеспечивает поверхностное клонирование. В этом механизме клонирования объект копируется без содержащихся в нем объектов.
Мелкий клон копирует только структуру верхнего уровня объекта, а не нижние уровни.
Как сделать глубокую копию объекта на Java
Узнайте о четырех способах создания глубокой копии объекта на Java и о том, почему следует предпочесть глубокую копию мелкой копии.
1. введение
Когда мы хотим скопировать объект на Java, нам необходимо рассмотреть две возможности: поверхностную копию и глубокую копию.
При подходе с неглубоким копированием мы копируем только значения полей, поэтому копия может зависеть от исходного объекта. При подходе с глубоким копированием мы следим за тем, чтобы все объекты в дереве были глубоко скопированы, поэтому копия не зависит от какого-либо ранее существующего объекта, который может когда-либо измениться.
В этом уроке мы сравним эти два подхода и изучим четыре метода реализации глубокой копии.
2. Настройка Maven
Мы будем использовать три зависимости Maven, Gson, Jackson и Apache Commons Lang, чтобы протестировать различные способы выполнения глубокого копирования.
Давайте добавим эти зависимости в ваш pom.xml :
3. Модель
Чтобы сравнить различные методы копирования объектов Java, нам понадобятся два класса для работы:
4. Мелкая Копия
Неглубокая копия-это та, в которой мы копируем только значения полей из одного объекта в другой:
Мы бы не беспокоились об этом, если бы Адрес был неизменным, но это не так:
5. Глубокое Копирование
Поскольку копия не зависит от какого-либо изменяемого объекта, созданного ранее, она не будет изменена случайно, как мы видели с неглубокой копией.
В следующих разделах мы обсудим несколько реализаций глубокого копирования и продемонстрируем это преимущество.
5.1. Конструктор копирования
Первая реализация, которую мы рассмотрим, основана на конструкторах копирования:
В приведенной выше реализации глубокой копии мы не создали новые Строки в нашем конструкторе копирования, потому что Строка является неизменяемым классом.
В результате они не могут быть изменены случайно. Давайте посмотрим, сработает ли это:
5.2. Клонируемый Интерфейс
Мы также добавим интерфейс маркера, Клонируемый, к классам, чтобы указать, что классы действительно клонируются.
Давайте добавим clone() метод в Адрес класс:
Теперь давайте реализуем clone() для Пользователя класса:
Обратите внимание, что супер.клон() вызов возвращает неглубокую копию объекта, но мы вручную устанавливаем глубокие копии изменяемых полей, поэтому результат правильный:
6. Внешние библиотеки
Это может произойти, когда мы не владеем кодом или когда граф объектов настолько сложен, что мы не закончили бы наш проект вовремя, если бы сосредоточились на написании дополнительных конструкторов или реализации метода clone для всех классов в графе объектов.
Давайте рассмотрим несколько примеров.
6.1. Язык Apache Commons Lang
В Apache Commons Lang есть SerializationUtils#clone, который выполняет глубокое копирование, когда все классы в графе объектов реализуют Сериализуемый интерфейс.
Следовательно, нам нужно добавить Сериализуемый интерфейс в наши классы:
6.2. Сериализация JSON С Помощью Json
Другой способ сериализации-использовать сериализацию JSON. Gson-это библиотека, которая используется для преобразования объектов в JSON и наоборот.
Давайте быстро рассмотрим пример:
6.3. Сериализация JSON С Джексоном
Давайте рассмотрим пример:
7. Заключение
Какую реализацию мы должны использовать при создании глубокой копии? Окончательное решение часто будет зависеть от классов, которые мы будем копировать, и от того, владеем ли мы классами в графе объектов.
Русские Блоги
Что вы знаете о поверхностном и глубоком копировании Java?
Это точка знания, которую мы обсуждаем сегодня в технологической группе. Обсуждение довольно интенсивное. Поскольку эта область используется меньше, в этой области есть некоторые белые пятна. Эта статья резюмирует то, что обсуждалось, и я надеюсь, что эта статья будет вам полезна.
В разработке на Java обычным явлением является копирование или клонирование объектов, а клонирование объектов в конечном итоге неразрывно.Прямое назначение、Мелкая копия、Глубокая копия Из этих трех методов наиболее распространенным должно быть прямое присвоение. Для поверхностного и глубокого копирования его можно использовать реже, поэтому возникают более или менее недопонимания. В этой статье эти три объекта будут подробно описаны. Режим клонирования.
Предварительные знания
Прямое назначение
Запустите приведенный выше код, вы получите следующие результаты:
Мелкая копия
Неглубокая копия также может реализовать клонирование объекта. Из этого названия вы можете знать, что у такого типа копии должны быть некоторые недостатки. Да, у нее есть определенные недостатки. Давайте посмотрим на определение неглубокой копии:Если переменная-член объекта-прототипа является типом значения, она будет скопирована в объект-клон, что означает, что у нее есть независимое пространство в куче; если переменная-член объекта-прототипа является ссылочным типом, адрес ссылочного объекта будет скопирован в клон Object, то есть переменные-члены объекта-прототипа и клонированного объекта указывают на один и тот же адрес памяти. Другими словами, при поверхностном клонировании, когда объект копируется, копируются только переменные-члены самого объекта и содержащийся в них тип значения, в то время как объект-член ссылочного типа не копируется. Возможно, вы не слишком хорошо понимаете этот отрывок, поэтому давайте посмотрим на общую модель мелкой копии:
Реализовать мелкую копию объекта относительно просто. Только скопированный класс должен реализовать интерфейс Cloneable и переписать метод клонирования. Класс человека можно преобразовать для поддержки неглубокого копирования.
Преобразование очень простое, просто позвольте человеку унаследовать интерфейс Cloneable и переписать метод clone. Клон также очень прост. Просто вызовите метод clone объекта. Единственное, что следует отметить, это то, что метод clone должен быть украшен общедоступным. Измените основной метод
Повторно запустите основной метод, результаты будут следующими:
Видя такой результат, сомневаетесь в этом? Указанный ссылочный объект просто скопировал адрес, почему значение атрибута name объекта person1 изменено, но объект person не изменился? Это очень важный момент, потому что:Классы упаковки, такие как String и Integer, являются неизменяемыми объектами.Когда вам нужно изменить значение неизменяемого объекта, вам нужно сгенерировать новый объект в памяти для хранения нового значения, а затем указать исходную ссылку на новый адрес. Здесь мы изменяем значение атрибута name объекта person1. Поле name объекта person1 указывает на новый объект name в памяти, но мы не изменили точку поля name объекта person, поэтому имя объекта person по-прежнему указывает на исходное имя в памяти Адрес, без изменений
Измените основной метод
Запускаем основной метод и получаем следующий результат:
После того, как мы изменили поле desc для person1, desc of person1 также изменилось, что означает, что объект person и объект person1 указывают на один и тот же адрес объекта PersonDesc, что также соответствует определению ссылочного объекта с неглубокой копией, копирующего только ссылочный адрес, но не создающего новый объект. К настоящему времени вы должны знать поверхностную копию.
Глубокая копия
Реализовать метод интерфейса Cloneable
Способ реализации интерфейса Cloneable не сильно отличается от способа поверхностного копирования. Нам необходимо реализовать интерфейс Cloneable для объекта, на который имеется ссылка. Конкретное преобразование кода выглядит следующим образом:
Основной метод не требует изменений, запускаем снова основной метод и получаем следующие результаты:
Можно видеть, что изменение описания человека1 не влияет на описание человека, указывая, что была выполнена глубокая копия и новый объект регенерирован в памяти.
Реализовать метод интерфейса Serializable
Реализация метода интерфейса Serializable также может обеспечить глубокое копирование, и этот метод также может решить проблему многослойного клонирования. Многослойное клонирование означает, что в ссылочном типе есть ссылочные типы, а вложение является вложенным. Еще сложнее реализовать его в Cloneable. Если вы допустили ошибку по ошибке, вы не сможете добиться глубокого копирования. Использование сериализации с сериализацией требует, чтобы все объекты реализовали интерфейс сериализуемого объекта. Мы модифицируем код и преобразуем его в метод сериализации.
Запустив основной метод, мы можем получить тот же результат, что и метод Cloneable, а метод сериализации также реализует глубокую копию. На этом мы завершаем введение поверхностного и глубокого копирования Java, и я надеюсь, что вы кое-что узнали.
В конце концов
Приглашаем отсканировать QR-код, чтобы подписаться на официальный аккаунт WeChat: «Технический блог Pingtou», Pingtou будет вместе учиться и вместе добиваться прогресса.
Интерфейсы-маркеры, глубокое клонирование
— Сегодня я расскажу тебе про интерфейсы-маркеры.
Интерфейсы-маркеры – это интерфейсы, которые не содержат методов. Когда класс наследуется от такого интерфейса, то говорят, что он им помечен.
Примеры таких интерфейсов: Cloneable, Serializable, Remote.
Интерфейс Serializable используется, чтобы помечать классы, которые поддерживают сериализацию — как доказательство того, что объекты класса можно автоматически сериализовать и десериализовать.
Интерфейс Remote используется, чтобы обозначать объекты, которые поддерживают удаленный вызов – вызов из другой Java-машины и/или другого компьютера.
Интерфейс Cloneable используется, чтобы помечать классы, которые поддерживают клонирование.
Кстати, о клонировании.
Клонирование делится на два типа – обычное клонирование и глубокое клонирование.
Обычное клонирование – это когда создается дубликат только указанного объекта, без его внутренних объектов.
Глубокое клонирование – это когда создается дубликат объекта, объектов, на которые он ссылается, объектов, на которые ссылаются они и т.д.
Есть очень хороший способ выполнить качественное глубокое клонирование.
Этот способ подходит, даже если разработчики классов забыли пометить его интерфейсом Cloneable. Достаточно, чтобы объекты были сериализуемыми.
Вот что можно сделать:
1) Создать буфер (массив байт) в памяти.
2) Сериализовать в него нужный объект с подобъектами.
3) Десериализовать из буфера копию сохраненной в него группы объектов.
На третьей строчке мы создаем ByteArrayOutputStream – массив байт, который будет динамически растягиваться при добавлении к нему новых данных (как ArrayList).
На 8-й строчке мы преобразовываем writeBuffer в обычный массив байт. Дальше мы из этого массива будем «читать» наш новый объект.
Кстати, когда код раскрашен разными цветами – гораздо легче его понимать.
Независимое глубокое клонирование объектов в JavaScript
Как типы данных передаются в функции в JavaScript? Каждый js-программист наверняка без труда ответит на этот вопрос, но все же скажем: примитивные типы данных передаются в функцию всегда только по значению, а ссылочные всегда только по ссылке. И вот тут с последними, в некоторых ситуациях, возникают проблемы. Давайте рассмотрим пример:
В данном случае, мы просто объявили массив чисел и вывели его в консоли. Теперь передадим его в функцию, которая возвращает новый массив, но с добавлением некоторого значения во втором аргументе, к концу нового массива:
Старый массив не изменился, потому что мы получили каждый его элемент и по отдельности присвоили значения элемента к элементам нового массива. Теперь последний имеет отдельную область памяти и если его изменить, то старого массива это никак не коснется. Но все это простые примеры и в реальных программах, скорее всего будут встречаться не только одномерные массивы, а и двумерные, реже трехмерные, еще реже четырехмерные. Преимущественно они встречаются в виде ассоциативных массивов (хеш-таблицы). В JavaScript чаще всего это объекты.
Давайте рассмотрим стандартные способы копирования объектов, которые предоставляет JavaScript — Object.assign() используется для копирования значений всех собственных перечисляемых свойств из одного или более исходных объектов в целевой объект. После копирования он возвращает целевой объект. Рассмотрим его:
И снова старая проблема, мы ссылаемся на одну и ту же область памяти, что приводит к модификации двух объектов сразу — изменяя один будет изменяться и другой. Что же делать, если нам нужно получить копию сложного объекта (с множественным разветвлением) и при этом, изменяя объект, не модифицировать другой? Ответить на этот вопрос и есть цель данной статьи. Дальше мы рассмотрим, как написать собственный метод глубокого клонирования (копирования) объектов любого ветвления. Преступим к написанию кода.
2 шаг: присваиваем объект Z объекту refToZ для того, чтобы показать разницу между обычным присваиванием и глубоким клонированием:
5 шаг: на последнем шаге мы просто для сравнения к объекту refToZ добавим свойство addToZ со значением 100 и выведем все три объекта в консоль:
Немного остановимся над реализацией данной функции. Последняя находит, любую вложенность объекта, даже не зная ее. Как она это делает? Все дело в том, что в данном случае мы применяем известный алгоритм для графов — поиск в глубину. Объект — граф, который имеет одну или множество веток, которые в свою очередь могут иметь свои ветки и тд. Чтобы нам найти все нам нужно зайти в каждую ветку и продвигаться в ее глубь, таким образом мы найдем каждый узел в графе и получим его значения. Поиск в глубину можно реализовать 2 способами: рекурсией и с помощью цикла. Второй может оказаться быстрее, так как не будет заполнять стек вызовов, что в свою очередь делает рекурсия. В нашей реализации функции deepClone мы применили комбинацию рекурсию с циклом. Если хотите почитать книги об алгоритмах, то советую начать Адитъя Бхаргава «Грокаем алгоритмы» или более углубленное Томас Кормен «Алгоритмы: построение и анализ».
Подведем итоги, мы вспомнили об типах данных в JavaScript и как они передаются в функции. Рассмотрели простой пример независимого клонирования простого одномерного массива. Рассмотрели одну из стандартных реализации языка для копирования объектов и в итоге написали маленький (по размеру) функцию для независимого глубокого клонирования сложных объектов. Подобная функция может найти свое применения как на стороне сервера (Node js), что более вероятно, так и на клиенте. Надеюсь данная статья была полезна для вас. До новых встреч.