В чем проблема использования статических методов с точки зрения архитектуры и многопоточности в Java
Что проверяют
Отличаете ли вы безобидную статическую функцию от глобального состояния, спрятанного за статикой. Интервьюер ждёт, что вы не объявите «статика это плохо» целиком, и следом обычно спрашивает, когда статический метод уместен и как тестировать код, который его вызывает.
Как отвечать
Начните с разделения: чистый статический метод без состояния (Math.max, List.of) проблем не создаёт. Проблемы начинаются в трёх местах.
Архитектура:
- жёсткая связь: вызов
PaymentGateway.charge()прибит к конкретной реализации, подменить её через DI или конфигурацию нельзя; - нет полиморфизма: статический метод не переопределяется, а скрывается, вызов связывается при компиляции по объявленному типу;
- скрытые зависимости: по конструктору не видно, что класс ходит в статический кэш или реестр, а Spring не внедряет бины в статические поля.
Тестирование: Mockito умеет mockStatic (inline mock maker стал поведением по умолчанию в Mockito 5), но такой мок действует только в текущем потоке и внутри try-with-resources. Это обход, а не дизайн.
Многопоточность:
- общее изменяемое состояние: статическое поле одно на класс в загрузчике, его видят все потоки. Классика: статический
SimpleDateFormat, который ломается при параллельных вызовах, вместо него берут неизменяемыйDateTimeFormatter; static synchronized: блокирует объектClass, то есть один замок на все вызовы, отсюда конкуренция потоков;- утечки: статическая коллекция-кэш растёт без ограничений и держит объекты до выгрузки класса.
Полезно добавить, что инициализация класса потокобезопасна силами JVM, на этом построен ленивый синглтон через holder-класс.
Где спотыкаются
Говорят, что статические методы сами по себе не потокобезопасны. Опасно разделяемое изменяемое состояние, а метод без полей можно вызывать из любого числа потоков.
Предлагают заменить статику синглтоном. Это то же глобальное состояние в другой обёртке, решение здесь DI и явная передача зависимостей.
Не знают про скрытие методов. Вызов через переменную типа Parent, в которой лежит наследник, выполнит статический метод Parent, а кандидат ждёт поведения наследника.