Вопрос 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. Когда нужно делать defensive copy]]
  • [[12. Как защитить коллекцию от изменений]]
  • [[14. В чём разница между shallow copy и deep copy]]