Ответ
Fail-fast — это принцип проектирования, при котором система немедленно сообщает об ошибке при обнаружении некорректного состояния, вместо того чтобы продолжать работу с повреждёнными данными.
Основная идея: Лучше упасть сразу с понятной ошибкой, чем продолжать выполнение с неопределённым поведением.
Классический пример в Java — коллекции:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) { // Неявно использует Iterator
if (item.equals("B")) {
list.remove(item); // ConcurrentModificationException!
}
}
Как это работает:
- Итератор содержит счётчик
modCount(количество изменений коллекции) - При каждой операции модификации коллекции
modCountувеличивается - При вызове
next()илиhasNext()итератор проверяет, чтоmodCountне изменился - Если изменился — бросается
ConcurrentModificationException
Реализация в ArrayList.iterator():
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
public E next() {
checkForComodification(); // Проверка изменений
// ...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
Fail-fast vs Fail-safe итераторы:
| Тип | Пример | Поведение при изменении |
|---|---|---|
| Fail-fast | ArrayList, HashMap, HashSet | ConcurrentModificationException |
| Fail-safe | CopyOnWriteArrayList, ConcurrentHashMap | Работает с копией данных |
Правильное удаление элементов:
// Способ 1: Использовать Iterator.remove()
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove(); // Безопасно!
}
}
// Способ 2: Java 8+ removeIf
list.removeIf(item -> item.equals("B"));
// Способ 3: Собрать элементы для удаления
List<String> toRemove = list.stream()
.filter(item -> item.equals("B"))
.collect(Collectors.toList());
list.removeAll(toRemove);
Применение за пределами коллекций:
-
Валидация входных данных:
public void process(User user) { Objects.requireNonNull(user, "User cannot be null"); // Fail-fast if (user.getAge() < 0) { throw new IllegalArgumentException("Age cannot be negative"); } // ... обработка } -
Инварианты в конструкторах:
public class Account { private final BigDecimal balance; public Account(BigDecimal initialBalance) { if (initialBalance.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("Balance cannot be negative"); } this.balance = initialBalance; } } -
Состояние объекта:
public void transfer(Account to, BigDecimal amount) { if (this.isClosed()) { throw new IllegalStateException("Account is closed"); } if (amount.compareTo(balance) > 0) { throw new IllegalArgumentException("Insufficient funds"); } // ... выполнение перевода }
Преимущества Fail-fast:
- Раннее обнаружение ошибок — ближе к источнику проблемы
- Упрощение отладки — стектрейс указывает на реальную причину
- Предсказуемость — система ведёт себя детерминировано
- Безопасность — предотвращение повреждения данных
Недостатки:
- Может создавать излишние проверки в production-коде
- Требует аккуратной обработки исключений на верхних уровнях
Когда использовать:
- При разработке библиотек и API
- В критических системах (финансовые, медицинские)
- При работе с shared mutable state
- На этапе разработки и тестирования