W3docs

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 um T de 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 same

Em 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 thread

Em 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 empty

Essa 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.

java— editable, runs on the server

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 único Consumer<String> que pode ser passado para forEach como qualquer outro.
  • O encadeamento com andThen que começou com boom parou na exceção — never nunca foi invocado. andThen é sequencial, não engole exceções.
  • present.orElseGet(expensive) deixou o supplier intocado porque o optional estava presente, enquanto present.orElse(expensive.get()) avaliou a chamada cara antes mesmo de ser necessária. O contador de chamadas é a prova — essa é a lacuna que o Supplier existe para preencher.
  • Stream.generate(ids).limit(3) produziu três UUIDs chamando get() exatamente três vezes. O supplier é a fonte lazy de um stream ilimitado — limit é o que torna o pipeline finito.
  • IntConsumer add se encaixou diretamente em IntStream.forEach e evitou o boxing de cada inteiro no intervalo. Use a especialização primitiva sempre que estiver dentro de um stream primitivo.
  • BooleanSupplier underFive mostrou a forma que o JDK usa para o Stream.iterate de 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.

Prática

Prática
Você está escrevendo `String name = userOpt.orElseXxx(...)` e o valor padrão é `loadDefaultName()`, que leva vários segundos porque acessa um banco de dados. Você quer que esse carregamento ocorra *somente* se `userOpt` estiver vazio. Qual chamada está correta?
Você está escrevendo `String name = userOpt.orElseXxx(...)` e o valor padrão é `loadDefaultName()`, que leva vários segundos porque acessa um banco de dados. Você quer que esse carregamento ocorra *somente* se `userOpt` estiver vazio. Qual chamada está correta?
Was this page helpful?