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

Що таке захисна копія (defensive copy)?

Як ксерокопія документа: ви віддаєте копію, і якщо хтось на ній пише, ваш оригінал не страждає.

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

Junior Level

Захисна копія (Defensive Copy) — це створення копії даних, щоб захистити внутрішній об’єкт від змін ззовні.

Як ксерокопія документа: ви віддаєте копію, і якщо хтось на ній пише, ваш оригінал не страждає.

Простий приклад

public class MyClass {
    private Date date;

    public MyClass(Date date) {
        // ПОГАНО: this.date = date; // зовнішня змінна може змінитися
        // ДОБРЕ:
        this.date = new Date(date.getTime()); // створили копію
    }

    public Date getDate() {
        return new Date(date.getTime()); // повертаємо копію
    }
}

Навіщо це потрібно

Без копії код, що викликає, може змінити ваші внутрішні дані через посилання:

Date d = new Date();
MyClass obj = new MyClass(d);
d.setYear(2000); // Змінили внутрішній об'єкт MyClass!

Коли НЕ робити defensive copy

  1. Незмінні типи (String, Integer, LocalDate) — копіювати безглуздо
  2. Повністю контрольований internal API — caller guaranteed не мутувати
  3. Performance-critical код — копіювання кожного виклику створює unacceptable overhead

Middle Level

Дві точки захисту

1. В конструкторі (вхід даних)

public class MyObject {
    private final Date startDate;
    private final List<String> items;

    public MyObject(Date date, List<String> items) {
        this.startDate = new Date(date.getTime());
        this.items = new ArrayList<>(items);
    }
}

2. В гетері (вихід даних)

public Date getStartDate() {
    return new Date(startDate.getTime());
}

public List<String> getItems() {
    return Collections.unmodifiableList(new ArrayList<>(items));
}

Shallow vs Deep Copy

  • Shallow Copy — копіюється контейнер, але елементи залишаються тими самими
    List<String> copy = new ArrayList<>(original);
    
  • Deep Copy — рекурсивно копіюються усі вкладені об’єкти
    List<User> copy = original.stream()
        .map(u -> new User(u.getName(), u.getAge()))
        .toList();
    

Сучасний підхід (Java 10+)

this.items = List.copyOf(items); // незмінна копія

Senior Level

TOCTOU та порядок операцій

Копіюйте перед валідацією:

public SecureClass(List<String> roles) {
    List<String> copy = new ArrayList<>(roles); // 1. Копія
    if (copy.contains("admin")) {               // 2. Перевірка
        // ...
    }
    this.roles = List.copyOf(copy);             // 3. Збереження
}

Інакше можливий TOCTOU-attack: дані зміняться між перевіркою та використанням.

Wrapper vs Copy

Collections.unmodifiableList(original) — це обгортка, не копія. Якщо original зміниться, обгортка теж зміниться. Використовуйте лише для повернення з гетера, але не в конструкторі.

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

Захисне копіювання — операція $O(n)$. У високонавантажених системах:

  • Уникайте копіювання в гарячих циклах
  • Використовуйте персистентні структури (Vavr) для структурного розділення
  • Кешувати копії можна лише якщо дані дійсно статичні; інакше ризикуєте повернути застарілі дані.

Резюме для Senior

  • Defensive copy — створення “знімка” змінних даних
  • Обов’язкова умова для незмінності за наявності полів-об’єктів
  • Завжди робіть копію перед валідацією
  • Collections.unmodifiableList() — обгортка, а не копія; використовуйте List.copyOf() для справжнього захисту

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

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

  • Defensive Copy — створення копії даних для захисту від змін ззовні
  • Дві точки захисту: конструктор (вхід) та гетер (вихід)
  • Shallow Copy — копіюється контейнер, Deep Copy — рекурсивно усі вкладені об’єкти
  • TOCTOU: копіюйте ПЕРЕД валідацією, інакше дані зміняться між перевіркою та використанням
  • Wrapper vs Copy: unmodifiableList — обгортка (O(1)), List.copyOf — копія (O(n))
  • Коли НЕ робити: незмінні типи, internal API, performance-critical код

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

  • Що дешевше — обгортка чи копія? — Обгортка O(1), копія O(n); але обгортка не захищає від змін оригіналу
  • Коли shallow copy достатньо? — Коли колекція містить незмінні об’єкти (String, Integer)
  • Що таке TOCTOU-атака? — Дані змінюються між перевіркою та копіюванням
  • List.copyOf vs new ArrayList? — List.copyOf повертає незмінний список; new ArrayList — змінний

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

  • «UnmodifiableList — це копія» — це обгортка, зміни оригіналу видимі
  • «Defensive copy завжди потрібен» — для незмінних типів безглуздий
  • «clone() — надійний спосіб» — clone() вважається broken (Effective Java Item 13)
  • «Копіювання після валідації — правильно» — потрібно КОПІЮВАТИ ПЕРЕД валідацією

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

  • [[8. Чи достатньо зробити всі поля final для незмінності]]
  • [[9. Що робити, якщо поле класу посилається на змінний об’єкт]]
  • [[11. Коли потрібно робити захисну копію]]
  • [[12. Як захистити колекцію від змін]]
  • [[14. В чому різниця між shallow copy та deep copy]]