Вопрос 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. Как правильно работать с коллекциями в иммутабельных классах]]