Java Consumer e Supplier
Consumer com efeitos colaterais e Supplier produtor de valores: interfaces funcionais em Java explicadas com exemplos práticos.
Consumer<T> e Supplier<T> são as duas interfaces funcionais dos cantos não puros da taxonomia de quatro cantos:
Consumer<T>recebe um valor e retorna nada — seu objetivo é o efeito colateral (imprimir, registrar, escrever, inserir em uma coleção).Supplier<T>não recebe nada e retorna um valor — seu objetivo é produzir umTde forma lazy, sob demanda (valores padrão, fábricas, aleatoriedade).
Ambas se complementam com os capítulos sobre Function/Predicate que vieram antes: esses retornavam um valor a partir de um valor, enquanto esses entram e saem do mundo ao redor. Este capítulo cobre ambas as interfaces porque suas APIs são pequenas e seus casos de uso se sobrepõem.
Consumer<T>
@FunctionalInterface
public interface Consumer<T> {
void accept(T t); // the only abstract method
default Consumer<T> andThen(Consumer<? super T> after);
}Um Consumer é "faça algo com este T." O SAM é accept. O único método default andThen encadeia consumidores para que sejam executados em sequência sobre a mesma entrada:
Consumer<String> log = System.out::println;
Consumer<String> store = audit::record;
Consumer<String> both = log.andThen(store);
both.accept("hello"); // prints "hello", then audit.record("hello")andThen não sofre curto-circuito se o primeiro consumidor lançar uma exceção — ela se propaga, e o segundo consumidor nunca é executado. Essa é a mesma semântica de escrever as duas chamadas em um bloco sem try: a falha interrompe a sequência.
Onde Consumer<T> aparece
list.forEach(System.out::println); // Iterable.forEach(Consumer)
stream.forEach(System.out::println); // Stream.forEach
optional.ifPresent(name -> log.info(name)); // Optional.ifPresent
queue.peek(System.out::println); // not a Consumer call, but the shape is the sameEm qualquer lugar que o JDK diz "faça algo com cada elemento", o parâmetro é um Consumer<T> ou um BiConsumer<K, V> para casos com dois argumentos (mais notavelmente Map.forEach((k, v) -> ...)).
BiConsumer<T, U>
A variante com dois argumentos:
BiConsumer<String, Integer> show = (k, v) -> System.out.println(k + " => " + v);
Map<String, Integer> scores = Map.of("alice", 1, "bob", 2);
scores.forEach(show);BiConsumer tem o mesmo andThen padrão. Não existe BiSupplier — um Supplier com dois argumentos seria apenas um BiFunction<T, U, R>.
Especializações primitivas — IntConsumer, LongConsumer, DoubleConsumer
IntConsumer printInt = System.out::println; // accepts int, no boxing
LongConsumer tally = n -> total += n;
DoubleConsumer record = d -> samples.add(d);Mesma semântica do andThen. IntStream.forEach aceita um IntConsumer, por isso um stream primitivo pode chamar sua lambda sem boxing.
Existem também ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T> para o caso em que um argumento é um objeto e o outro é um primitivo — Stream.collect(Supplier, BiConsumer, BiConsumer) e seus equivalentes primitivos os utilizam.
Supplier<T>
@FunctionalInterface
public interface Supplier<T> {
T get(); // the only abstract method
}Essa é a interface completa — sem métodos default, sem andThen, sem composição. O motivo é que um Supplier tem a forma mais simples possível: zero entradas, uma saída, e a única coisa que se pode fazer com ele é chamar get().
Supplier<List<String>> empty = ArrayList::new;
Supplier<UUID> id = UUID::randomUUID;
Supplier<String> expensive = () -> loadFromDb();Onde Supplier<T> aparece
Supplier é a forma do JDK de escrever lazy — "me dê este valor, mas apenas quando eu precisar":
opt.orElseGet(() -> loadDefault()); // lazy default
Objects.requireNonNullElseGet(value, () -> sentinel); // lazy default for null
Stream.generate(() -> Math.random()).limit(5); // infinite stream of supplied values
logger.debug("expensive: {}", () -> serialiseGraph(state)); // lazy log argument
CompletableFuture.supplyAsync(() -> compute()); // run the supplier on another threadEm todo lugar que um Supplier<T> aparece no JDK, o contrato é "este valor pode nunca ser necessário." Optional.orElseGet só chama get() quando o optional está vazio; Stream.generate só o chama quando o próximo elemento é solicitado. Essa preguiça é o objetivo — um argumento T simples já teria sido computado no momento em que o método fosse invocado.
Especializações primitivas — IntSupplier, LongSupplier, DoubleSupplier, BooleanSupplier
IntSupplier count = () -> counter.getAndIncrement();
DoubleSupplier random = Math::random;
BooleanSupplier ready = sensor::isReady;Supplier<Boolean> funciona, mas o primitivo BooleanSupplier é o que o JDK usa para portões de curto-circuito (Stream.iterate, IntStream.iterate na forma de três argumentos aceitam um BooleanSupplier ou IntPredicate como teste hasNext).
Supplier versus um argumento T simples
A regra geral:
- Passe um valor quando o custo de calculá-lo é negligível ou quando você definitivamente precisará dele.
- Passe um
Supplier<T>quando o custo importa e o receptor pode não precisar do valor.
opt.orElse(loadDefaultFromDb()); // bad: loadDefaultFromDb() runs whether opt is present or not
opt.orElseGet(() -> loadDefaultFromDb()); // good: loadDefaultFromDb() runs only when opt is emptyEssa diferença é a razão mais comum pela qual orElseGet é preferido em relação a orElse em código de produção.
Exemplo prático: Consumer.andThen, preguiça do Supplier, variantes primitivas
O programa abaixo constrói dois consumidores e os encadeia com andThen, demonstra a diferença de avaliação entre orElse e orElseGet com um contador, gera um pequeno stream a partir de um Supplier, e associa IntConsumer a IntStream.forEach sem que ocorra autoboxing.
O que extrair da execução:
log.andThen(store)executou ambos os consumidores sobre a mesma entrada, na ordem de declaração. O rastro de auditoria mostrou ambas as chamadas; o encadeamento se tornou um únicoConsumer<String>que pode ser passado paraforEachcomo qualquer outro.- O encadeamento com
andThenque começou comboomparou na exceção —nevernunca foi invocado.andThené sequencial, não engole exceções. present.orElseGet(expensive)deixou o supplier intocado porque o optional estava presente, enquantopresent.orElse(expensive.get())avaliou a chamada cara antes mesmo de ser necessária. O contador de chamadas é a prova — essa é a lacuna que oSupplierexiste para preencher.Stream.generate(ids).limit(3)produziu três UUIDs chamandoget()exatamente três vezes. O supplier é a fonte lazy de um stream ilimitado —limité o que torna o pipeline finito.IntConsumer addse encaixou diretamente emIntStream.forEache evitou o boxing de cada inteiro no intervalo. Use a especialização primitiva sempre que estiver dentro de um stream primitivo.BooleanSupplier underFivemostrou a forma que o JDK usa para oStream.iteratede três argumentos e outros portões do tipo "continue até" — o supplier é verificado uma vez por iteração, de forma lazy.
O que vem a seguir
Você agora viu os quatro cantos: Function (entrada, saída), Predicate (entrada, boolean), Consumer (entrada, sem saída), Supplier (sem entrada, saída). O próximo capítulo, Java BinaryOperator e UnaryOperator, fecha a parte com as duas especializações em que todos os parâmetros compartilham o mesmo tipo — a forma que alimenta Stream.reduce, Map.merge e List.replaceAll.