Supplier в Java: функциональный интерфейс и метод get() - Вопросы

Всего: 5 вопросов

1. 

В каких случаях используется интерфейс Supplier?

Supplier<T> используется тогда, когда на вход передавать нечего, но результат нужен: интерфейс не принимает аргументов и возвращает объект типа T по вызову get(). Функциональный дескриптор — () -> T. Типичные сценарии: ленивое вычисление дорогого значения по умолчанию (Optional.orElseGet, Optional.orElseThrow, Objects.requireNonNullElseGet), фабрика объектов (ArrayList::new), источник новых значений при каждом вызове (Math::random, LocalDateTime::now), генерация потока в Stream.generate(Supplier) и асинхронное вычисление в CompletableFuture.supplyAsync(Supplier).

2. 

Напишите пример, который показывает, как интерфейс Supplier возвращает экземпляр объекта.

Лямбда без аргументов идеально ложится на дескриптор () -> T, поэтому Supplier удобно использовать как фабрику объектов:

import java.util.function.Supplier;

public class Animal {
    public void move() {
        System.out.println("Move");
    }

    @Override
    public String toString() {
        return "Some Animal";
    }
}

public class TestAnimal {
    public static void main(String[] args) {
        Supplier<Animal> s = () -> new Animal();
        System.out.println(s.get()); // Some Animal
    }
}

Поскольку лямбда только вызывает конструктор без аргументов, её можно заменить ссылкой на конструктор: Supplier<Animal> s = Animal::new;. Важно, что каждый вызов get() создаёт новый объект — Supplier ничего не кэширует.

3. 

Чем метод orElse отличается от orElseGet у класса Optional?

orElse(value) принимает уже вычисленное значение, поэтому выражение внутри скобок выполняется всегда — даже если Optional не пуст. orElseGet(supplier) принимает Supplier и вызывает get() только для пустого Optional. Если значение по умолчанию дорогое (запрос в базу, создание объекта), нужен именно orElseGet.

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));
4. 

Чем Supplier отличается от Callable и можно ли бросить проверяемое исключение из get()?

Функциональные дескрипторы у них совпадают: оба интерфейса ничего не принимают и возвращают значение — () -> T. Разница в двух вещах. Во-первых, Callable.call() объявлен как throws Exception, а Supplier.get() не объявляет throws вовсе, поэтому проверяемое исключение внутри лямбды Supplier просто не скомпилируется. Во-вторых, Callable живёт в пакете java.util.concurrent и предназначен для задач, отправляемых в ExecutorService, а Supplier из java.util.function — для ленивого получения значения в обычном коде и в Stream API.

T get();                      // Supplier, java.util.function
V call() throws Exception;    // Callable, java.util.concurrent

Если проверяемое исключение всё же нужно, есть три варианта: обернуть его в непроверяемое (например, UncheckedIOException), взять Callable или описать собственный функциональный интерфейс с throws.

5. 

Какие примитивные варианты Supplier есть в пакете java.util.function и зачем они нужны?

Специализированные поставщики примитивов нужны, чтобы избежать автоупаковки: Supplier<Integer> в горячем коде создаёт лишний объект на каждой итерации. У каждого варианта свой метод — не get():

IntSupplier     counter = () -> 42;          // int getAsInt()
LongSupplier    clock   = System::nanoTime;  // long getAsLong()
DoubleSupplier  random  = Math::random;      // double getAsDouble()
BooleanSupplier ready   = () -> queue.isEmpty(); // boolean getAsBoolean()

Обратите внимание, что void-версии Supplier не существует: интерфейс создан именно для получения результата. Для действия без входа и без результата используется Runnable с методом run().

Страница 1 из 1