Питання 14 · Розділ 13

В чому різниця між shallow copy та deep copy?

на інші об'єкти (колекція, масив, будь-який клас з полями-посиланнями), але елементи всередині залишаються тими самими (ті самі посилання).

Мовні версії: English Russian Ukrainian

Junior Level

Shallow Copy (Поверхнева копія) — копіюється лише сам контейнер = об’єкт, що містить посилання на інші об’єкти (колекція, масив, будь-який клас з полями-посиланнями), але елементи всередині залишаються тими самими (ті самі посилання).

Deep Copy (Глибока копія) — копіюється контейнер і усі об’єкти всередині нього.

Приклад

class User {
    String name;
    User(String name) { this.name = name; }
}

List<User> original = new ArrayList<>();
original.add(new User("Ivan"));

// Shallow Copy
List<User> shallow = new ArrayList<>(original);

// Тепер обидва списки посилаються на одного й того ж User:
shallow.get(0).name = "Petr";
System.out.println(original.get(0).name); // "Petr" — змінився і в оригіналі!

Аналогія

  • Shallow Copy — зробити фотокопію коробки, але залишити вміст спільним
  • Deep Copy — зробити фотокопію коробки і скопіювати кожну річ всередині

Middle Level

Shallow Copy

  • Копіюються: примітивні поля + посилання на об’єкти
  • Оригінал і копія ділять одні й ті самі вкладені об’єкти
  • Механізми: new ArrayList<>(original), array.clone()
  • Object.clone() — за замовчуванням робить shallow copy. Для deep copy потрібно перевизначити clone() і вручну клонувати вкладені об’єкти.

Deep Copy

  • Копіюються: все, включаючи вкладені об’єкти (рекурсивно)
  • Оригінал і копія повністю незалежні
  • Механізми:
    1. Конструктори копіювання — в кожному класі конструктор, що приймає об’єкт того ж типу
    2. Серіалізація — через ObjectOutputStream / ObjectInputStream
    3. JSON — серіалізація в JSON і назад
    4. Бібліотеки — Apache Commons SerializationUtils.clone()

Приклад Deep Copy

public class User implements Cloneable {
    String name;
    Address address; // змінний об'єкт

    @Override
    public User clone() {
        User u = new User();
        u.name = this.name;              // String незмінний
        u.address = this.address.clone(); // глибока копія
        return u;
    }
}

Коли що використовувати

Сценарій Shallow Deep
Колекція String / Integer Достатньо Зайве
Колекція змінних об’єктів Небезпечно Обов’язково
Складність O(1) або O(n) для контейнера O(n × depth) — рекурсивно по всьому графу

Senior Level

Shallow Immutability — джерело прихованих багів

Поверхнева незмінність — найчастіша причина багів:

public record Config(Map<String, User> users) {
    public Config {
        users = Map.copyOf(users); // Shallow — користувачі ті самі
    }
}
// Хтось робить: config.users().get("admin").setRole("superadmin")

Продуктивність Deep Copy

  • $O(n \times m)$ — де n = розмір контейнера, m = глибина графа
  • Серіалізація: дуже повільно, створює багато об’єктів
  • Конструктори копіювання: швидко, але багато ручного коду

Structural Sharing

Персистентні структури даних (Vavr, PCollections) використовують structural sharing — при зміні копіюється лише шлях до елемента, а решту гілок спільні. Це дає $O(\log n)$ замість $O(n)$.

Резюме для Senior

  • Shallow Copy — копіює “верхушку айсберга”; підходить для колекцій незмінних об’єктів
  • Deep Copy — копіює всю структуру; необхідна для справжньої незмінності
  • Завжди уточнюйте глибину копіювання при проектуванні API
  • Для великих графів розгляньте персистентні структури з structural sharing

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Shallow Copy — копіюється контейнер, елементи ті самі (ті самі посилання)
  • Deep Copy — копіюється контейнер і усі вкладені об’єкти рекурсивно
  • Shallow достатньо для колекцій String/Integer; Deep обов’язковий для змінних об’єктів
  • Механізми Deep Copy: конструктори копіювання, серіалізація, JSON, бібліотеки
  • Object.clone() — за замовчуванням shallow; для deep потрібно перевизначити
  • Structural Sharing (Vavr) — $O(\log n)$ замість $O(n)$ при копіюванні
  • Shallow Immutability — джерело прихованих багів: контейнер незмінний, елементи mutable

Часті уточнюючі запитання:

  • Коли Shallow Copy небезпечна? — Коли колекція містить змінні об’єкти
  • Deep Copy — які механізми? — Конструктори копіювання (швидко), серіалізація (повільно), JSON
  • Що таке Structural Sharing? — При зміні копіюється лише шлях, решту гілок спільні (Vavr)
  • Object.clone() робить deep copy? — Ні, за замовчуванням shallow; потрібно перевизначити

Червоні прапорці (НЕ говорити):

  • «Shallow Copy = повна копія» — лише контейнер, елементи ті самі
  • «Object.clone() за замовчуванням deep» — ні, shallow
  • «Deep Copy завжди потрібен» — для String/Integer колекцій зайвий
  • «Серіалізація — швидкий спосіб» — дуже повільно, багато об’єктів

Пов’язані теми:

  • [[8. Чи достатньо зробити всі поля final для незмінності]]
  • [[10. Що таке захисна копія (defensive copy)]]
  • [[12. Як захистити колекцію від змін]]
  • [[24. Що таке persistent data structures]]
  • [[29. Як правильно працювати з колекціями в незмінних класах]]