W3docs

Java Consumer y Supplier

Consumer con efectos secundarios y Supplier productor de valores: interfaces funcionales clave en Java.

Consumer<T> y Supplier<T> son las dos interfaces funcionales para los extremos no puros de la taxonomía de cuatro esquinas:

  • Consumer<T> recibe un valor y devuelve nada — su trabajo es el efecto secundario (imprimir, registrar, escribir, insertar en una colección).
  • Supplier<T> no recibe nada y devuelve un valor — su trabajo es producir un T de forma perezosa, bajo demanda (valores predeterminados, fábricas, aleatoriedad).

Ambas se complementan con los capítulos de Function/Predicate anteriores: aquellas devolvían un valor a partir de un valor; estas entran y salen del mundo circundante. Este capítulo cubre ambas interfaces porque sus APIs son pequeñas y sus sitios de uso se solapan.

Consumer<T>

@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);                                         // the only abstract method

  default Consumer<T> andThen(Consumer<? super T> after);
}

Un Consumer es "hacer algo con este T." El SAM es accept. El único método default andThen encadena consumidores para que se ejecuten en secuencia sobre la misma 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 no hace cortocircuito si el primer consumidor lanza una excepción — deja que la excepción se propague y el segundo consumidor nunca se ejecuta. Tiene la misma semántica que escribir las dos llamadas en un bloque sin try: el fallo detiene la secuencia.

Dónde aparece Consumer<T>

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

Donde el JDK dice "hacer algo con cada elemento", el parámetro es un Consumer<T> o un BiConsumer<K, V> para casos de dos argumentos (más notablemente Map.forEach((k, v) -> ...)).

BiConsumer<T, U>

La variante de dos 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 tiene el mismo default andThen. No existe BiSupplier — un Supplier de dos argumentos sería simplemente un BiFunction<T, U, R>.

Especializaciones primitivas — IntConsumer, LongConsumer, DoubleConsumer

IntConsumer    printInt = System.out::println;       // accepts int, no boxing
LongConsumer   tally    = n -> total += n;
DoubleConsumer record   = d -> samples.add(d);

Misma semántica de andThen. IntStream.forEach acepta un IntConsumer, razón por la que un stream primitivo puede llamar tu lambda sin boxing.

También existen ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T> para el caso en que un argumento es un objeto y el otro es un primitivo — Stream.collect(Supplier, BiConsumer, BiConsumer) y sus variantes primitivas los utilizan.

Supplier<T>

@FunctionalInterface
public interface Supplier<T> {
  T get();                                                   // the only abstract method
}

Esa es la interfaz completa — sin métodos default, sin andThen, sin composición. La razón es que un Supplier es la forma más simple posible: cero entradas, una salida, y lo único que puedes hacer con él es llamar a get().

Supplier<List<String>> empty = ArrayList::new;
Supplier<UUID>         id    = UUID::randomUUID;
Supplier<String>       expensive = () -> loadFromDb();

Dónde aparece Supplier<T>

Supplier es la forma del JDK de escribir lazy — "dame este valor, pero solo cuando lo necesite":

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

Cada vez que aparece un Supplier<T> en el JDK, el contrato es "este valor puede que nunca sea necesario." Optional.orElseGet solo llama a get() cuando el optional está vacío; Stream.generate solo lo llama cuando se demanda el siguiente elemento. Esa pereza es el punto central — un argumento T simple ya habría sido computado cuando se invocó el método.

Especializaciones primitivas — IntSupplier, LongSupplier, DoubleSupplier, BooleanSupplier

IntSupplier     count   = () -> counter.getAndIncrement();
DoubleSupplier  random  = Math::random;
BooleanSupplier ready   = sensor::isReady;

Supplier<Boolean> funciona, pero el BooleanSupplier primitivo es lo que el JDK usa para puertas de cortocircuito (Stream.iterate, IntStream.iterate en su forma de tres argumentos toman un BooleanSupplier o IntPredicate como prueba hasNext).

Supplier versus un argumento T simple

La regla general:

  • Pasa un valor cuando el costo de calcularlo es despreciable o cuando definitivamente lo necesitarás.
  • Pasa un Supplier<T> cuando el costo importa y el receptor puede no necesitar el valor.
opt.orElse(loadDefaultFromDb());          // bad: loadDefaultFromDb() runs whether opt is present or not
opt.orElseGet(() -> loadDefaultFromDb()); // good: loadDefaultFromDb() runs only when opt is empty

Esa diferencia es la razón más común por la que se prefiere orElseGet sobre orElse en código de producción.

Ejemplo práctico: Consumer.andThen, pereza de Supplier, variantes primitivas

El programa a continuación construye dos consumidores y los encadena con andThen, demuestra la diferencia de evaluación entre orElse y orElseGet con un contador, genera un stream pequeño desde un Supplier, y combina IntConsumer con IntStream.forEach para evitar el autoboxing.

java— editable, runs on the server

Lo que se puede extraer de la ejecución:

  • log.andThen(store) ejecutó ambos consumidores sobre la misma entrada, en orden de declaración. El registro de auditoría mostró ambas llamadas; la cadena se convirtió en un único Consumer<String> que podías pasar a forEach como cualquier otro.
  • La cadena andThen que comenzó con boom se detuvo en la excepción — never no fue invocado nunca. andThen es secuencial, no absorbe excepciones.
  • present.orElseGet(expensive) dejó el supplier intacto porque el optional estaba presente, mientras que present.orElse(expensive.get()) evaluó la llamada costosa antes de que fuera necesaria. El contador de llamadas es la prueba — esa es la brecha que Supplier existe para cubrir.
  • Stream.generate(ids).limit(3) produjo tres UUIDs llamando a get() exactamente tres veces. El supplier es la fuente perezosa de un stream ilimitado — limit es lo que hace la tubería finita.
  • IntConsumer add se conectó directamente a IntStream.forEach y evitó el boxing de cada entero en el rango. Usa la especialización primitiva siempre que estés dentro de un stream primitivo.
  • BooleanSupplier underFive mostró la forma que el JDK usa para la forma de tres argumentos de Stream.iterate y otras puertas de "continuar hasta que" — el supplier se comprueba una vez por iteración, de forma perezosa.

Qué sigue

Ahora has visto las cuatro esquinas: Function (entrada, salida), Predicate (entrada, boolean), Consumer (entrada, sin salida), Supplier (sin entrada, salida). El siguiente capítulo, Java BinaryOperator y UnaryOperator, cierra la parte con las dos especializaciones donde cada parámetro comparte el mismo tipo — la forma que impulsa Stream.reduce, Map.merge y List.replaceAll.

Práctica

Práctica
Estás escribiendo `String name = userOpt.orElseXxx(...)` y el valor predeterminado es `loadDefaultName()`, que tarda varios segundos porque accede a una base de datos. Quieres que esa carga se ejecute *solo* si `userOpt` está vacío. ¿Cuál llamada es correcta?
Estás escribiendo `String name = userOpt.orElseXxx(...)` y el valor predeterminado es `loadDefaultName()`, que tarda varios segundos porque accede a una base de datos. Quieres que esa carga se ejecute *solo* si `userOpt` está vacío. ¿Cuál llamada es correcta?
Was this page helpful?