ProIT: медіа для профі в IT
4 хв

Як копіювати об’єкти в Java: поверхнева копія і глибока копія

author avatar ProIT NEWS

Копіювання об’єктів – звичайна операція програмування, яка має одну серйозну пастку. Як уникнути копіювання з посилання на об’єкт і копіювати лише потрібний екземпляр і значення, повідомляє InfoWorld.

Копіювання об’єктів є звичайною операцією у корпоративних проєктах. Під час копіювання об’єкта ми повинні переконатися, що в кінцевому підсумку отримаємо новий екземпляр, який містить потрібні значення.

Об’єкти домену зазвичай складні. Створення копії з кореневим об’єктом і складеними об’єктами також не є тривіальним.

Розглянемо найефективніші способи копіювання об’єкта за допомогою методів поверхневого і глибокого копіювання.

Об’єктні посилання

Щоб правильно виконати дрібне або глибоке копіювання об’єкта, ми повинні спочатку знати, чого не слід робити. Розуміння посилань на об’єкти є важливим для використання методів поверхневого і глибокого копіювання.

Роблячи копію об’єкта, важливо уникати використання того самого посилання на об’єкт. Для початку ось об’єкт Product, який ми будемо використовувати у наших прикладах:

public class Product { private String name; private double price; public Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } public void setName(String name) { this.name = name; } public void setPrice(double price) { this.price = price; } }

Тепер давайте призначимо посилання на об’єкт Product іншій змінній. Начебто копія, але насправді це той самий об’єкт:

public static void main(String[] args) { Product product = new Product("Macbook Pro", 3000); Product copyOfProduct = product; product.name = "Alienware"; System.out.println(product.name); System.out.println(copyOfProduct.name); }

Результатом цього коду є:

Alienware Alienware

Зауважте, що у коді вище ми присвоюємо значення об’єкта іншій локальній змінній, але ця змінна вказує на те саме посилання на об’єкт. Якщо ми змінюємо об’єкти product або copyOfProduct, результатом буде зміна оригінального об’єкта Product.

Це тому, що кожного разу, коли ми створюємо об’єкт у Java, у масиві пам’яті Java створюється посилання на об’єкт. Це дає нам змогу змінювати об’єкти за допомогою їхніх посилальних змінних.

Неглибока копія

Техніка поверхневого копіювання дозволяє копіювати прості значення об’єкта в новий об’єкт, не включаючи внутрішні значення об’єкта. Як приклад, ось як використовувати техніку поверхневого копіювання для копіювання об’єкта Product без використання посилання на його об’єкт:

// Omitted the Product object public class ShallowCopyPassingValues { public static void main(String[] args) { Product product = new Product("Macbook Pro", 3000); Product copyOfProduct = new Product(product.getName(), product.getPrice()); product.setName("Alienware"); System.out.println(product.getName()); System.out.println(copyOfProduct.getName()); } }

Результатом цього коду є:

Alienware Macbook Pro

Зверніть увагу в цьому коді, що коли ми передаємо значення від одного об’єкта до іншого, у купі пам’яті створюються два різні об’єкти. Коли ми змінюємо одне зі значень у новому об’єкті, значення залишаться незмінними у вихідному об’єкті. Це доводить, що об’єкти різні, і ми успішно виконали поверхневу копію.

Примітка. Шаблон проєктування Builder – це ще один спосіб виконання тієї ж дії.

Неглибока копія із Cloneable

Починаючи з Java 7, у нас є інтерфейс Cloneable у Java. Цей інтерфейс надає інший спосіб копіювання об’єктів. Замість реалізації логіки копіювання вручну, як ми щойно зробили, ми можемо реалізувати інтерфейс Cloneable, а потім реалізувати метод clone(). Використання Cloneable та методу clone() автоматично призводить до дрібної копії.

Ви повинні вручну ввести тип класу, що робить код багатослівним. Але використання Cloneable може спростити код, якщо ми маємо величезний об’єкт домену із багатьма атрибутами.

Ось що станеться, якщо ми реалізуємо інтерфейс Cloneable в об’єкт домену, а потім перевизначимо метод clone():

public class Product implements Cloneable { // Omitted attributes, methods and constructor @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }

Ось знову метод копіювання в дії:

public class ShallowCopyWithCopyMethod { public static void main(String[] args) throws CloneNotSupportedException { Product product = new Product("Macbook Pro", 3000); Product copyOfProduct = (Product) product.clone(); product.setName("Alienware"); System.out.println(product.getName()); System.out.println(copyOfProduct.getName()); } }

Як бачите, метод копіювання ідеально працює для створення поверхневої копії об’єкта. Його використання означає, що нам не потрібно копіювати кожен атрибут вручну.

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

Техніка глибокого копіювання – це можливість копіювати значення складеного об’єкта в інший новий об’єкт. Наприклад, якщо об’єкт Product містить об’єкт Category, очікується, що всі значення з обох об’єктів буде скопійовано до нового об’єкта.

Що станеться, якщо об’єкт Product має складений об’єкт? Чи спрацює техніка поверхневого копіювання? Давайте подивимося, що станеться, якщо ми спробуємо використати лише метод copy().

Для початку ми створюємо клас Product з об’єктом Order:

public class Product implements Cloneable { // Omitted other attributes, constructor, getters and setters private Category category; public Category getCategory() { return category; } }

Тепер давайте зробимо те саме за допомогою методу super.clone():

public class TryDeepCopyWithClone { public static void main(String[] args) throws CloneNotSupportedException { Category category = new Category("Laptop", "Portable computers"); Product product = new Product("Macbook Pro", 3000, category); Product copyOfProduct = (Product) product.clone(); Category copiedCategory = copyOfProduct.getCategory(); System.out.println(copiedCategory.getName()); } }

Результатом цього коду є:

Laptop

Зауважте, що навіть незважаючи на те, що результатом є Laptop, операція глибокого копіювання не відбулася. Натомість у нас є те саме посилання на об’єкт Category. Ось доказ:

public class TryDeepCopyWithClone { public static void main(String[] args) throws CloneNotSupportedException { // Same code as the example above copiedCategory.setName("Phone"); System.out.println(copiedCategory.getName()); System.out.println(category.getName()); } }

Результат:

Laptop Phone Phone

Оскільки ми вручну скопіювали метод категорії в метод copy() продукту, він нарешті працює. Ми отримаємо копію із Product і Category за допомогою методу copy() з Product.

Цей код доводить, що глибока копія спрацювала. Значення вихідного та скопійованого об’єктів різні. Отже, це не той самий екземпляр, це скопійований об’єкт.

Висновки

Іноді техніка поверхневого копіювання – це все, що вам потрібно для поверхневого клонування об’єкта. Але якщо ви хочете скопіювати й об’єкт, і його внутрішні об’єкти, то ви повинні реалізувати глибоку копію вручну. Ось ключові висновки з цих важливих технік.

Що слід пам’ятати про неглибоке копіювання

Неглибока копія створює новий об’єкт, але має спільні посилання на внутрішні об’єкти з оригінальним об’єктом.

Скопійовані й оригінальні об’єкти посилаються на ті самі об’єкти в пам’яті. Зміни, внесені до внутрішніх об’єктів за допомогою одного посилання, будуть відображені як у скопійованих, так і в оригінальних об’єктах.

Неглибоке копіювання – простий та ефективний процес. Java надає стандартну реалізацію поверхневого копіювання через метод clone().

Що потрібно пам’ятати про глибоке копіювання

Глибока копія створює новий об’єкт, а також створює нові копії його внутрішніх об’єктів. Скопійовані й оригінальні об’єкти мають незалежні копії внутрішніх об’єктів.

Зміни, внесені до внутрішніх об’єктів через одне посилання, не вплинуть на інше.

Глибоке копіювання є більш складним процесом, особливо коли ви маєте справу із графами об’єктів або вкладеними посиланнями. Глибоке копіювання має бути реалізовано явно, вручну або за допомогою бібліотек чи фреймворків.

Раніше ProIT описував нові функції у Java 22.

Також ProIT повідомляв, що Java 18 Simple Web Server дає змогу використовувати інструмент командного рядка або API для розміщення файлів.

Підписуйтеся на ProIT у Telegram, щоб не пропустити жодну публікацію!

Приєднатися до company logo
Продовжуючи, ти погоджуєшся з умовами Публічної оферти та Політикою конфіденційності.