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), andSupplier<T>(T get).Two-parameter extensions include
BiPredicate<T, U>,BiConsumer<T, U>, andBiFunction<T, U, R>; there is no BiSupplier in the standard library.UnaryOperator<T>extendsFunction<T, T>andBinaryOperator<T>extendsBiFunction<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().
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
| Interface | SAM Signature | Functional 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>: ExtendsFunction<T, T>. Its single abstract method isT apply(T t).BinaryOperator<T>: ExtendsBiFunction<T, T, T>. Its single abstract method isT 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 aBinaryOperator<T>that selects the lesser of two elements according to the comparator.BinaryOperator.maxBy(Comparator<T> comp): Returns aBinaryOperator<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 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
| Interface | SAM Signature | Return Value | Exam Notes |
|---|---|---|---|
IntPredicate | boolean test(int value) | boolean | Also LongPredicate, DoublePredicate |
IntConsumer | void accept(int value) | void | Also LongConsumer, DoubleConsumer |
IntSupplier | int getAsInt() | int | Method is getAsInt(), NOT get()! |
LongSupplier | long getAsLong() | long | Method is getAsLong() |
DoubleSupplier | double getAsDouble() | double | Method is getAsDouble() |
BooleanSupplier | boolean getAsBoolean() | boolean | Takes 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:
- 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)
- 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)
- 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)(alsoObjLongConsumer<T>,ObjDoubleConsumer<T>).IntUnaryOperator:int applyAsInt(int operand)(alsoLongUnaryOperator,DoubleUnaryOperator).IntBinaryOperator:int applyAsInt(int left, int right)(alsoLongBinaryOperator,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 evaluatesotherusing short-circuiting logical AND (&&).default Predicate<T> or(Predicate<? super T> other): Evaluates this predicate, then evaluatesotherusing 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 equaltargetRefviaObjects.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): Appliesthisfunction first, and then appliesafterto the resulting value: .default <V> Function<V, R> compose(Function<? super V, ? extends T> before): Appliesbeforefirst, and then appliesthisto the resulting value: .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): Executesthisconsumer, followed byafterwith the same input. Ifthisthrows an exception,afteris 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!
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?
Prints false false
Throws a NullPointerException when evaluating the empty string ""
Prints false followed by a NullPointerException
Prints false true
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?
42
54
34
27
Which of the following code snippets compiles and executes without throwing an exception or compiler error?
IntSupplier supplier = () -> 100; int value = supplier.get();
IntPredicate isPositive = x -> x > 0; boolean check = isPositive.test(10.5);
ObjIntConsumer<String> logger = (s, i) -> System.out.println(s + i); logger.accept("Port: ", 8080);
ToIntFunction<String> lengthFinder = s -> s.length(); int len = lengthFinder.apply("Open");
Which statement regarding functional interfaces in java.util.function is correct?
UnaryOperator<T> cannot be assigned to a reference of type Function<T, T> because they are separate interfaces.
BiSupplier<T, U> is provided in java.util.function to generate pairs of objects.
UnaryOperator<T> extends Function<T, T>, and BinaryOperator<T> extends BiFunction<T, T, T>.
BinaryOperator<T> introduces a new abstract method named applyBinary(T t1, T t2).
Sections you finish are checked off in the contents.