Supplier в Java: функциональный интерфейс и метод get() - Вопросы
Всего: 5 вопросов
1. В каких случаях используется интерфейс Supplier?
В каких случаях используется интерфейс 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 возвращает экземпляр объекта.
Напишите пример, который показывает, как интерфейс 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 отличается от 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()?
Чем 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 есть в пакете 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().