В чем отличие Supplier'а от Consumer'а

В Java Supplier и Consumer — это два различных функциональных интерфейса, входящих в пакет java.util.function, каждый из которых играет свою роль в функциональном программировании и использовании лямбда-выражений.

Они представляют собой две противоположные концепции: один поставляет данные (возвращает значение), другой — потребляет данные (принимает значение и ничего не возвращает).

Интерфейс Supplier<T>

Supplier<T> представляет операцию, которая ничего не принимает, но возвращает значение типа T.

Он используется, когда нужно получить или "поставить" данные без входных параметров, например, при создании новых объектов, генерации случайных значений или отложенной инициализации.

Сигнатура интерфейса:

@FunctionalInterface
public interface Supplier&lt;T&gt; {
T get();
}

Единственный абстрактный метод:

T get();

Примеры использования:

Supplier&lt;String&gt; stringSupplier = () -> "Привет, мир!";
System.out.println(stringSupplier.get()); // выводит: Привет, мир!
Supplier&lt;Integer&gt; randomSupplier = () -> new Random().nextInt(100);
System.out.println(randomSupplier.get()); // случайное число от 0 до 99

Supplier часто используется:

  • для ленивой инициализации (отложенное получение данных),

  • при генерации значений,

  • в некоторых структурах данных (например, Optional.orElseGet(Supplier)),

  • при создании фабрик объектов.

Интерфейс Consumer<T>

Consumer<T> представляет операцию, которая принимает один аргумент типа T, но ничего не возвращает.

Он используется, когда нужно выполнить операцию над значением, например, вывести его на экран, записать в файл, сохранить в базу данных и т.д. Это пример "потребления" данных.

Сигнатура интерфейса:

@FunctionalInterface
public interface Consumer&lt;T&gt; {
void accept(T t);
}

Единственный абстрактный метод:

void accept(T t);

Примеры использования:

Consumer&lt;String&gt; printer = s -> System.out.println("Печать: " + s);
printer.accept("Привет"); // выводит: Печать: Привет
List&lt;String&gt; names = List.of("Аня", "Борис", "Вика");
names.forEach(s -> System.out.println(s.toUpperCase()));

Consumer полезен:

  • при обработке коллекций (например, с методом forEach()),

  • в обработке событий и колбэках,

  • для логирования или вывода значений,

  • при применении действий к каждому элементу.

Сравнение по смыслу:

Критерий Supplier<T> Consumer<T>
Что делает Поставляет/генерирует значение Потребляет значение
--- --- ---
Метод T get() void accept(T t)
--- --- ---
Аргументы Нет аргументов Принимает один аргумент
--- --- ---
Возвращаемое значение Да, возвращает T Нет, ничего не возвращает
--- --- ---
Примеры использования Optional.orElseGet, генерация, фабрики forEach, логирование, обработка
--- --- ---
Тип операции Без входа, но с выходом С входом, но без выхода
--- --- ---

Взаимодействие с другими функциональными интерфейсами

  • Supplier<T> похож на фабрику: он создаёт и отдаёт значение, но не использует аргументы.

  • Consumer<T> похож на обработчик: он получает значение и делает что-то с ним.

  • Если нужен и вход, и выход — применяется Function<T, R>, где T — вход, R — выход.

  • Если нужен булевый результат — используется Predicate<T>.

Пример совместного использования:

Supplier&lt;String&gt; messageSupplier = () -> "Hello from supplier";
Consumer&lt;String&gt; messagePrinter = msg -> System.out.println("Consumer got: " + msg);
String message = messageSupplier.get();
messagePrinter.accept(message);

В этом примере:

  • Supplier генерирует строку,

  • Consumer принимает строку и выводит её.

Таким образом, можно строить цепочки действий: одни компоненты поставляют данные (Supplier), а другие обрабатывают их (Consumer).