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

Коли потрібно робити захисну копію (defensive copy)?

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

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

Junior Level

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

Коли робити копію

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

// Для повністю незмінного класу немає сетерів.
// Цей приклад показує Builder pattern або конструктор з копіюванням:
public class MyClass {
    private final List<String> items;

    public MyClass(List<String> items) {
        this.items = new ArrayList<>(items); // defensive copy в конструкторі
    }
}

2. При поверненні даних (в гетері)

public List<String> getItems() {
    return new ArrayList<>(items); // копія вихідних даних
}

Коли НЕ потрібно робити копію

  • Примітиви (int, boolean) — вони не змінні
  • String — незмінний
  • Внутрішні дані, які ніколи не покидають клас

Middle Level

Основні сценарії

А. Масиви

public class MyClass {
    private final int[] numbers;

    public MyClass(int[] numbers) {
        this.numbers = numbers.clone(); // або Arrays.copyOf(numbers)
    }

    public int[] getNumbers() {
        return numbers.clone();
    }
}

Б. Колекції

this.list = List.copyOf(input);        // Java 10+
this.list = new ArrayList<>(input);    // Java 8

В. Date

this.date = new Date(date.getTime());

Коли копіювання НЕ потрібне

  1. Незмінні типиString, BigDecimal, LocalDate, Integer
  2. Private дані — об’єкт створено всередині класу і ніколи не покидає його
  3. Transfer Objects — якщо клас за дизайном є змінним DTO
  4. Mutable DTO / Builder pattern — об’єкти за дизайном змінні
  5. Примітиви та обгортки (int, Integer, String) — незмінні за природою

Copy vs Unmodifiable

  • В конструкторі — лише Copy (new ArrayList(original))
  • В гетері — можна Copy або Unmodifiable (Collections.unmodifiableList(original))

// List.copyOf викидає NPE при null елементах, new ArrayList — допускає.


Senior Level

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

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

public SecureConfig(Map<String, String> config) {
    Map<String, String> copy = new HashMap<>(config); // спочатку копія
    if (!copy.containsKey("auth")) throw new IllegalStateException(); // потім перевірка
    this.config = Map.copyOf(copy);
}

Без цього в багатопотоковому середовищі мапа може змінитися після перевірки, але до збереження.

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

  • List.copyOf() — оптимізований в JDK 10+, може повернути той самий об’єкт якщо вхід вже незмінний
  • Для великих колекцій у гарячих циклах розгляньте персистентні структури даних (Vavr)
  • Уникайте глибокого копіювання величезних графів об’єктів

Резюме для Senior

  • Робіть копії всього, що може бути змінено ззовні
  • Спочатку копіюйте, потім валідуйте
  • Використовуйте List.copyOf() для сучасного захисного копіювання
  • Не копіюйте величезні масиви в “гарячих” циклах без необхідності
  • Розрізняйте обгортку та копію: обгортка не захищає від змін оригіналу

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

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

  • Копіювати при отриманні (конструктор) та при поверненні (гетер) змінних даних
  • НЕ потрібно копіювати: примітиви, String, LocalDate, Integer, private дані
  • Масиви: numbers.clone() або Arrays.copyOf()
  • Колекції: List.copyOf(input) (Java 10+) або new ArrayList<>(input) (Java 8)
  • Date: new Date(date.getTime())
  • Copy vs Unmodifiable: в конструкторі — лише Copy, в гетері — Copy або Unmodifiable
  • TOCTOU: спочатку копіюйте, потім валідуйте

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

  • Коли копіювання НЕ потрібне? — Незмінні типи, private дані, Transfer Objects
  • List.copyOf vs new ArrayList?* — List.copyOf повертає незмінний; new ArrayList — змінний
  • Що якщо колекція величезна? — Розгляньте персистентні структури (Vavr) або COW
  • List.copyOf допускає null? — Ні, викине NPE; new ArrayList допускає

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

  • «Defensive copy потрібен для всіх типів» — примітиви та String не потрібно
  • «Можна змінити змінний ключ в HashMap» — об’єкт загубиться в бакеті
  • «UnmodifiableList в конструкторі — захист» — це обгортка, не копія
  • «Копіювання після валідації — безпечно» — TOCTOU-атака можлива

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

  • [[9. Що робити, якщо поле класу посилається на змінний об’єкт]]
  • [[10. Що таке захисна копія (defensive copy)]]
  • [[12. Як захистити колекцію від змін]]
  • [[13. Що таке Collections.unmodifiableList() і як це працює]]
  • [[29. Як правильно працювати з колекціями в незмінних класах]]