Что такое defensive copy (защитная копия)?
Как ксерокопия документа: вы отдаёте копию, и если кто-то на ней пишет, ваш оригинал не страдает.
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
- Иммутабельные типы (String, Integer, LocalDate) — копировать бессмысленно
- Полностью контролируемый internal API — caller guaranteed не мутировать
- 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.copyOfvsnew 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]]