Supplier в Java: функциональный интерфейс и метод get()
Строка ниже выглядит безобидно, но каждый её вызов лезет в базу — даже когда пользователь найден:
User user = repository.findById(id).orElse(loadDefaultUserFromDb()); Аргумент orElse вычисляется всегда, потому что это обычное значение. Замените его на Supplier — и лишний запрос исчезнет: orElseGet(() -> loadDefaultUserFromDb()) сработает, только если Optional пуст. Ради такой отложенности Supplier и существует.
Supplier (в разговорной речи — «саплаер») — это встроенный функциональный интерфейс, добавленный в Java SE 8 в пакет java.util.function. Он не принимает аргументов и возвращает объект типа T: поставляет («supply») значение по запросу.
Что такое Supplier в Java: сигнатура и дескриптор
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Простыми словами: Supplier — это «поставщик» значения. Он не спрашивает ничего на входе и по вызову get() отдаёт готовый объект. Интерфейс используется тогда, когда на вход передавать нечего, но результат нужен. Функциональный дескриптор:
() -> T Единственный абстрактный метод get() вызывается каждый раз заново. Он может возвращать одно и то же значение (константу, синглтон) либо каждый раз новое (случайное число, текущее время, свежий объект) — это решает реализация, а не сам интерфейс.
Важно
Supplier ничего не кэширует. Создание объекта Supplier не запускает вычисление — код внутри лямбды выполняется только в момент вызова get() и выполняется столько раз, сколько раз вы этот метод вызвали.
Примеры использования Supplier
Первый и самый простой пример Supplier в Java — лямбда, которая отдаёт строку в верхнем регистре:
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
String t = "One";
Supplier<String> supplierStr = () -> t.toUpperCase();
System.out.println(supplierStr.get()); // ONE
}
} Второй частый вариант — ссылка на конструктор: сигнатура конструктора без аргументов идеально ложится на дескриптор () -> T.
Supplier<List<String>> listFactory = ArrayList::new;
List<String> first = listFactory.get();
List<String> second = listFactory.get();
System.out.println(first == second); // false — каждый вызов создаёт новый список Третий вариант — источник новых значений при каждом вызове:
Supplier<Double> random = Math::random;
Supplier<LocalDateTime> now = LocalDateTime::now;
System.out.println(random.get()); // 0.734...
System.out.println(random.get()); // 0.118... — уже другое значение И четвёртый, прикладной: Supplier как параметр метода, чтобы вызывающий код сам решал, какой объект создавать.
public static <T> List<T> repeat(int count, Supplier<T> factory) {
List<T> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
result.add(factory.get());
}
return result;
}
List<StringBuilder> builders = repeat(3, StringBuilder::new); Ленивые вычисления: главный сценарий Supplier
Supplier нужен там, где значение может не понадобиться, а его вычисление стоит дорого. Метод принимает не готовый объект, а «рецепт» его получения и вызывает get() только при необходимости.
Optional<User> found = repository.findById(id);
// плохо: createGuest() выполняется всегда, даже если пользователь найден
User a = found.orElse(createGuest());
// хорошо: createGuest() выполнится только для пустого Optional
User b = found.orElseGet(() -> createGuest());
// исключение конструируется только тогда, когда оно действительно бросается
User c = found.orElseThrow(() -> new UserNotFoundException(id)); Тот же приём работает при логировании: конкатенация строк не выполняется, если уровень логирования выключен.
// java.util.logging, Java 8+
logger.fine(() -> "Отчёт: " + buildExpensiveReport()); Начиная с Java 9 в стандартной библиотеке есть и Objects.requireNonNullElseGet(obj, supplier) — та же идея для значений по умолчанию.
Где Supplier встречается в JDK
Даже если вы не объявляете Supplier сами, вы регулярно передаёте его в методы стандартной библиотеки:
Optional.orElseGet(Supplier),Optional.orElseThrow(Supplier),Optional.or(Supplier);Stream.generate(Supplier)— создаёт бесконечный поток, поэтому его обязательно ограничиваютlimit();Collectors.toCollection(Supplier)— когда нужен конкретный тип коллекции;CompletableFuture.supplyAsync(Supplier)— асинхронное вычисление результата;ThreadLocal.withInitial(Supplier)— начальное значение для каждого потока.
List<String> ids = Stream.generate(() -> UUID.randomUUID().toString())
.limit(3)
.collect(Collectors.toList());
TreeSet<String> sorted = names.stream()
.collect(Collectors.toCollection(TreeSet::new)); Примитивные варианты Supplier
Чтобы избежать автоупаковки, в java.util.function есть специализированные поставщики примитивов. У каждого свой метод — не get().
| Интерфейс | Метод | Возвращает | Когда использовать |
|---|---|---|---|
| Supplier<T> | T get() | Объект любого типа | Общий случай |
| IntSupplier | int getAsInt() | int | Счётчики, индексы, генерация чисел |
| LongSupplier | long getAsLong() | long | Метки времени, большие счётчики |
| DoubleSupplier | double getAsDouble() | double | Случайные и вещественные значения |
| BooleanSupplier | boolean getAsBoolean() | boolean | Отложенная проверка условия (например, в ожиданиях и ретраях) |
Обратите внимание
В отличие от Function с её andThen() и compose(), у Supplier нет ни одного default-метода — только get(). Скомбинировать два Supplier «из коробки» нельзя, композицию пишут вручную: () -> mapper.apply(source.get()).
Supplier vs Consumer, Function, Callable
Функциональные интерфейсы java.util.function различаются формой дескриптора: сколько аргументов принимают и что возвращают.
| Интерфейс | Метод | Дескриптор | Смысл |
|---|---|---|---|
| Supplier<T> | T get() | () → T | Ничего не принимает, отдаёт значение |
| Consumer<T> | void accept(T t) | T → void | Принимает значение, ничего не возвращает |
| Function<T, R> | R apply(T t) | T → R | Преобразует значение в другое |
| Predicate<T> | boolean test(T t) | T → boolean | Проверяет условие |
| Callable<V> | V call() throws Exception | () → V | То же, что Supplier, но может бросать проверяемые исключения |
| Runnable | void run() | () → void | Действие без входа и без результата |
Supplier и Consumer — зеркальная пара, их почти всегда объясняют вместе. Supplier ничего не принимает и отдаёт значение (() -> T), Consumer принимает значение и ничего не отдаёт (T -> void). В связке получается простейший конвейер: один поставляет данные, другой их обрабатывает.
Supplier<String> source = () -> "данные из файла";
Consumer<String> sink = System.out::println;
sink.accept(source.get()); // данные из файла Ключевая пара для собеседований — Supplier и Callable: сигнатуры почти одинаковые, но Callable.call() объявляет throws Exception, а Supplier.get() — нет. Отсюда и разное применение: Callable живёт в java.util.concurrent и рассчитан на задачи в ExecutorService, а Supplier — на ленивое получение значения.
Где чаще всего ошибаются
- Путают
orElseиorElseGet.orElseпринимает готовое значение и вычисляет его всегда;orElseGetпринимает Supplier и вызывает его только при пустомOptional. - Ждут кэширования. Каждый
get()выполняет тело лямбды заново. Если результат нужен один раз, мемоизацию придётся написать самому. - Пытаются бросить проверяемое исключение из тела лямбды — код не компилируется, потому что
get()не объявляетthrows. - Забывают
limit()послеStream.generate(...)— поток бесконечен, и терминальная операция зависает. - Используют
Supplier<Integer>в горячем коде вместоIntSupplier, получая лишнюю упаковку в цикле.
Мемоизирующая обёртка, если результат должен вычисляться ровно один раз:
public static <T> Supplier<T> memoize(Supplier<T> delegate) {
Map<String, T> cache = new ConcurrentHashMap<>();
return () -> cache.computeIfAbsent("value", k -> delegate.get());
} Совет
Переменные, захваченные лямбдой Supplier, должны быть effectively final. Если нужно отдавать меняющееся состояние, захватывайте не саму переменную, а объект (поле класса, AtomicInteger) и читайте его внутри get().
Полное описание интерфейса — в официальной документации Oracle.
Часто задаваемые вопросы
Что такое Supplier в Java простыми словами?
Supplier — это функциональный интерфейс из пакета java.util.function, добавленный в Java 8. У него один метод T get(): он не принимает аргументов и возвращает значение. Проще говоря, это «поставщик» объекта, который вызывается тогда, когда объект действительно понадобился.
Чем Supplier отличается от Consumer?
Это противоположные по направлению интерфейсы. Supplier<T> ничего не принимает и возвращает значение — дескриптор () -> T, метод get(). Consumer<T> принимает значение и ничего не возвращает — дескриптор T -> void, метод accept(). Supplier обычно стоит в начале цепочки (источник данных), Consumer — в конце (побочный эффект: печать, запись, отправка).
Чем orElse отличается от orElseGet?
orElse(value) принимает уже вычисленное значение, поэтому выражение внутри скобок выполняется всегда — даже если Optional не пуст. orElseGet(supplier) принимает Supplier и вызывает get() только для пустого Optional. Если значение по умолчанию дорогое (запрос в базу, создание объекта), нужен именно orElseGet.
Можно ли бросить проверяемое исключение из Supplier?
Нет. Метод get() не объявляет throws, поэтому checked-исключение внутри лямбды не скомпилируется. Варианты: обернуть его в непроверяемое (например, UncheckedIOException), использовать Callable, у которого call() объявлен как throws Exception, или описать собственный функциональный интерфейс с throws.
Кэширует ли Supplier результат вызова get()?
Нет, кэширования нет: тело лямбды выполняется при каждом вызове get(). Если значение должно вычисляться один раз, напишите мемоизирующую обёртку через ConcurrentHashMap.computeIfAbsent или воспользуйтесь готовым Suppliers.memoize из Guava.
Чем Supplier отличается от Callable?
Дескрипторы совпадают: оба ничего не принимают и возвращают значение. Разница в двух вещах: Callable.call() объявляет throws Exception, а Supplier.get() — нет; Callable живёт в java.util.concurrent и предназначен для задач в ExecutorService, тогда как Supplier из java.util.function используется для ленивого получения значения в обычном коде и в Stream API.
Video Explanation
Prefer video format? Watch this lesson with examples and explanations.
Comments