7.2 Standard Functional Interfaces in `java.util.function`

Key Takeaways

  • The java.util.function package organizes functional interfaces into four primary families: Predicate<T> (boolean test), Consumer<T> (void accept), Function<T, R> (R apply), and Supplier<T> (T get).

  • Two-parameter extensions include BiPredicate<T, U>, BiConsumer<T, U>, and BiFunction<T, U, R>; there is no BiSupplier in the standard library.

  • UnaryOperator<T> extends Function<T, T> and BinaryOperator<T> extends BiFunction<T, T, T>, ensuring that parameter types and the return type match exactly.

  • Primitive specializations eliminate boxing and unboxing overhead for int, long, and double; primitive suppliers use specialized getters (such as getAsInt() and getAsBoolean()) rather than get().

  • Default combinator methods enable functional chaining: Predicate supports and(), or(), and negate(); Function provides andThen() and compose(); and Consumer offers andThen().

Last updated: October 2026

7.2 Standard Functional Interfaces in java.util.function

Prior to Java 8, libraries frequently declared redundant single-method interfaces for callbacks, comparisons, and transformations. To standardize functional programming across the Java runtime, Java provides the java.util.function package containing 43 general-purpose functional interfaces. On the 1Z0-829 exam, you must recognize the four core functional families, their two-parameter extensions, operator specializations, primitive adaptations, and default composition methods.


The Four Foundational Families

Every functional interface in java.util.function derives conceptually from four core archetypes categorized by their input arity and return type:

                      +---------------------------------------+
                      |   Core Functional Interface Families  |
                      +---------------------------------------+
                                          |
         +-----------------+--------------+---------------+-----------------+
         |                 |                              |                 |
         v                 v                              v                 v
   Predicate<T>       Consumer<T>                   Function<T, R>     Supplier<T>
   boolean test(T)    void accept(T)                R apply(T)         T get()
   [Condition/Filter] [Side-effect/Action]          [Transformation]   [Factory/Generator]

1. Predicate<T>: boolean test(T t)

Evaluates a condition against an argument of type T and returns a primitive boolean. Primarily used for filtering, validation, and matching.

Predicate<String> isLong = s -> s != null && s.length() > 5;
boolean result = isLong.test("Enterprise"); // true

2. Consumer<T>: void accept(T t)

Accepts an argument of type T and executes a side-effect without returning a value. Primarily used for logging, printing, and mutating state.

Consumer<String> printer = s -> System.out.println("Log: " + s);
printer.accept("Transaction successful");

3. Function<T, R>: R apply(T t)

Transforms an input argument of type T into a result of type R. Forms the backbone of mapping operations in streams.

Function<String, Integer> lengthFinder = s -> s.length();
Integer len = lengthFinder.apply("Oracle"); // 6

4. Supplier<T>: T get()

Takes no arguments (()) and returns an instance of type T. Used for lazy evaluation, object factories, and generating values on demand.

Supplier<LocalDate> today = () -> LocalDate.now();
LocalDate currentDate = today.get();

Two-Parameter Extensions and Operators

Java extends the foundational families to handle two input arguments, as well as homogenous transformations.

Two-Parameter Variants

InterfaceSAM SignatureFunctional Purpose
BiPredicate<T, U>boolean test(T t, U u)Tests a condition involving two distinct input types.
BiConsumer<T, U>void accept(T t, U u)Consumes two arguments for side effects (e.g., Map.forEach).
BiFunction<T, U, R>R apply(T t, U u)Transforms two input types T and U into return type R.

Note

Notice there is no BiSupplier in java.util.function. A supplier takes zero arguments; having a "two-input supplier" contradicts the definition of a supplier (a two-input producer is a BiFunction).

Operators: Homogenous Functions

When a function's argument types and return type are identical, Java provides dedicated operator interfaces that extend Function and BiFunction:

  • UnaryOperator<T>: Extends Function<T, T>. Its single abstract method is T apply(T t).
  • BinaryOperator<T>: Extends BiFunction<T, T, T>. Its single abstract method is T apply(T t1, T t2).
UnaryOperator<String> clean = s -> s.trim().toLowerCase();
String res = clean.apply("  Java 17  "); // "java 17"

BinaryOperator<Integer> max = (x, y) -> x >= y ? x : y;
Integer highest = max.apply(42, 99); // 99

Static utility methods on BinaryOperator:

  • BinaryOperator.minBy(Comparator<T> comp): Returns a BinaryOperator<T> that selects the lesser of two elements according to the comparator.
  • BinaryOperator.maxBy(Comparator<T> comp): Returns a BinaryOperator<T> that selects the greater of two elements.

Primitive Specializations

Using standard generic interfaces such as Function<Integer, Double> forces the JVM to continuously box and unbox primitives (int ↔\leftrightarrow Integer), degrading performance and generating memory garbage. To optimize execution, java.util.function provides 34 specialized primitive interfaces centered on int, long, double, and boolean (the other 9 of its 43 interfaces are the generic ones such as Function and Supplier).

Primitive Predicates, Consumers, and Suppliers

InterfaceSAM SignatureReturn ValueExam Notes
IntPredicateboolean test(int value)booleanAlso LongPredicate, DoublePredicate
IntConsumervoid accept(int value)voidAlso LongConsumer, DoubleConsumer
IntSupplierint getAsInt()intMethod is getAsInt(), NOT get()!
LongSupplierlong getAsLong()longMethod is getAsLong()
DoubleSupplierdouble getAsDouble()doubleMethod is getAsDouble()
BooleanSupplierboolean getAsBoolean()booleanTakes no args, returns primitive boolean

Warning

The Primitive Supplier Method Name Trap Generic Supplier<T> declares T get(). However, primitive suppliers do not declare get(). They declare getAsInt(), getAsLong(), getAsDouble(), and getAsBoolean(). Calling .get() on an IntSupplier causes a compile error!

Primitive Functions and Conversions

Primitive functions fall into three structural categories:

  1. Primitive-to-Object Functions: Accept a primitive, return a generic reference R.
    • IntFunction<R>: R apply(int value)
    • LongFunction<R>: R apply(long value)
    • DoubleFunction<R>: R apply(double value)
  2. Object-to-Primitive Functions: Accept a generic reference T, return a primitive.
    • ToIntFunction<T>: int applyAsInt(T value)
    • ToLongFunction<T>: long applyAsLong(T value)
    • ToDoubleFunction<T>: double applyAsDouble(T value)
  3. Primitive-to-Primitive Conversions: Accept a primitive, return a different primitive.
    • IntToLongFunction: long applyAsLong(int value)
    • IntToDoubleFunction: double applyAsDouble(int value)
    • LongToIntFunction: int applyAsInt(long value)
    • LongToDoubleFunction: double applyAsDouble(long value)
    • DoubleToIntFunction: int applyAsInt(double value)
    • DoubleToLongFunction: long applyAsLong(double value)

Two-Argument Primitive Consumers and Operators

  • ObjIntConsumer<T>: void accept(T t, int value) (also ObjLongConsumer<T>, ObjDoubleConsumer<T>).
  • IntUnaryOperator: int applyAsInt(int operand) (also LongUnaryOperator, DoubleUnaryOperator).
  • IntBinaryOperator: int applyAsInt(int left, int right) (also LongBinaryOperator, DoubleBinaryOperator).

Default and Static Combinator Methods

Many functional interfaces contain default and static methods that allow developers to compose and chain multiple behaviors together.

1. Predicate<T> Combinators

  • default Predicate<T> and(Predicate<? super T> other): Evaluates this predicate, then evaluates other using short-circuiting logical AND (&&).
  • default Predicate<T> or(Predicate<? super T> other): Evaluates this predicate, then evaluates other using short-circuiting logical OR (||).
  • default Predicate<T> negate(): Inverts the predicate (!test(t)).
  • static <T> Predicate<T> isEqual(Object targetRef): Returns a predicate testing whether arguments equal targetRef via Objects.equals().
  • static <T> Predicate<T> not(Predicate<? super T> target): Added in Java 11, returns the negation of the target predicate (ideal with method references: Predicate.not(String::isBlank)).
Predicate<String> nonNull = s -> s != null;
Predicate<String> nonBlank = s -> !s.isBlank();
Predicate<String> valid = nonNull.and(nonBlank);

// Short-circuiting behavior prevents NullPointerException:
boolean check = valid.test(null); // nonNull returns false -> nonBlank is NEVER called!

2. Function<T, R> Combinators

  • default <V> Function<T, V> andThen(Function<? super R, ? extends V> after): Applies this function first, and then applies after to the resulting value: after(this(x))\text{after}(\text{this}(x)).
  • default <V> Function<V, R> compose(Function<? super V, ? extends T> before): Applies before first, and then applies this to the resulting value: this(before(x))\text{this}(\text{before}(x)).
  • static <T> Function<T, T> identity(): Returns a function that returns its input argument unchanged (t -> t).
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add10 = x -> x + 10;

// andThen: (x * 2) + 10
Function<Integer, Integer> f1 = multiplyBy2.andThen(add10);
System.out.println(f1.apply(5)); // (5 * 2) + 10 = 20

// compose: (x + 10) * 2
Function<Integer, Integer> f2 = multiplyBy2.compose(add10);
System.out.println(f2.apply(5)); // (5 + 10) * 2 = 30

3. Consumer<T> Combinators

  • default Consumer<T> andThen(Consumer<? super T> after): Executes this consumer, followed by after with the same input. If this throws an exception, after is not executed.
Consumer<String> step1 = s -> System.out.print("Hello " + s);
Consumer<String> step2 = s -> System.out.println("!");
Consumer<String> combined = step1.andThen(step2);
combined.accept("Duke"); // Prints: Hello Duke!
Test Your Knowledge

Consider the following code snippet:

Predicate<String> nonNull = s -> s != null;
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> containsAt = s -> s.contains("@");

Predicate<String> validator = nonNull.and(notEmpty).or(containsAt);
System.out.print(validator.test("") + " ");
System.out.print(validator.test(null));

What is the result of executing this code?

A

Prints false false

B

Throws a NullPointerException when evaluating the empty string ""

C

Prints false followed by a NullPointerException

D

Prints false true

Test Your Knowledge

Consider the following functional compositions:

Function<Integer, Integer> times3 = x -> x * 3;
Function<Integer, Integer> plus5 = x -> x + 5;

Function<Integer, Integer> pipeline = times3.compose(plus5).andThen(x -> x * 2);
System.out.println(pipeline.apply(4));

What does this code print?

A

42

B

54

C

34

D

27

Test Your Knowledge

Which of the following code snippets compiles and executes without throwing an exception or compiler error?

A

IntSupplier supplier = () -> 100; int value = supplier.get();

B

IntPredicate isPositive = x -> x > 0; boolean check = isPositive.test(10.5);

C

ObjIntConsumer<String> logger = (s, i) -> System.out.println(s + i); logger.accept("Port: ", 8080);

D

ToIntFunction<String> lengthFinder = s -> s.length(); int len = lengthFinder.apply("Open");

Test Your Knowledge

Which statement regarding functional interfaces in java.util.function is correct?

A

UnaryOperator<T> cannot be assigned to a reference of type Function<T, T> because they are separate interfaces.

B

BiSupplier<T, U> is provided in java.util.function to generate pairs of objects.

C

UnaryOperator<T> extends Function<T, T>, and BinaryOperator<T> extends BiFunction<T, T, T>.

D

BinaryOperator<T> introduces a new abstract method named applyBinary(T t1, T t2).

Sections you finish are checked off in the contents.