Java сначала вычисляет аргументы метода, и только потом вызывает сам метод.
Поэтому дорогой вызов справа выполнится заранее:
User user = users.getOrDefault(id, loadUser(id));
Даже если users уже содержит id, loadUser(id) успеет выполниться. Если это запрос к базе, сеть или вычисление, приложение потратит время зря.
Для ленивого вычисления значения подойдёт computeIfAbsent:
User user = users.computeIfAbsent(id, this::loadUser);
Функция загрузки вызывается только тогда, когда ключ отсутствует или связан с null. Возвращённое ненулевое значение добавляется в map и дальше берётся из неё.
Проверь и семантику отсутствующего результата. Если загрузчик вернёт null, запись не появится, и следующий вызов снова попробует загрузить пользователя:
User user = users.computeIfAbsent(id, key -> findUser(key).orElse(null));
Ещё один нюанс касается конкурентного доступа. Для параллельных потоков выбирай подходящую concurrent map и не помещай в функцию долгую блокирующую работу без понимания гарантий конкретной реализации.
👉 Java Ready | #совет
