7.1 Built-in Functional Interfaces in java.util.function
Key Takeaways
- A functional interface defines exactly one abstract method (Single Abstract Method or SAM contract), while permitting any number of default, static, and Object-inherited public methods.
- The four foundational functional interface families are Supplier<T> (T get()), Consumer<T> (void accept(T)), Predicate<T> (boolean test(T)), and Function<T, R> (R apply(T)).
- Bi-variants accept two arguments: BiConsumer<T, U>, BiPredicate<T, U>, and BiFunction<T, U, R>; there is no BiSupplier because methods in Java cannot return two distinct values.
- Primitive specializations (such as IntPredicate, ToLongFunction<T>, DoubleSupplier, and IntToDoubleFunction) optimize performance by eliminating autoboxing and unboxing overhead.
Built-in Functional Interfaces in java.util.function
Functional programming in Java revolves around functional interfaces—interfaces that declare exactly one abstract method. Introduced in Java 8 and continuously leveraged across modern Java standard APIs, functional interfaces act as target types for lambda expressions and method references. For the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam, you must demonstrate comprehensive mastery of the java.util.function package, including core families, binary variants, primitive specializations, operator sub-interfaces, and composition combinators.
1. Functional Interfaces & The SAM Contract
A functional interface is defined by its Single Abstract Method (SAM) contract. An interface is a valid functional interface if and only if it contains exactly one abstract method.
The @FunctionalInterface Annotation
The @FunctionalInterface annotation is optional, but it instructs the Java compiler to verify that the annotated interface strictly adheres to the SAM contract. If an annotated interface declares zero abstract methods or more than one abstract method, the compiler generates an error.
@FunctionalInterface
public interface StringTransformer {
String transform(String input); // Single Abstract Method (SAM)
// Default methods do NOT count toward SAM count
default String transformTwice(String input) {
return transform(transform(input));
}
// Static methods do NOT count toward SAM count
static StringTransformer identity() {
return s -> s;
}
// Public methods matching java.lang.Object signatures do NOT count toward SAM count
@Override
boolean equals(Object obj);
@Override
String toString();
}
[!IMPORTANT] Methods Matching
java.lang.Object: Any public method declared in an interface that overrides a public method ofjava.lang.Object(such asboolean equals(Object),int hashCode(), orString toString()) does not count toward the interface's abstract method count. This is because every implementing class automatically inherits an implementation fromjava.lang.Object.
2. The Four Core Functional Interface Families
All 43 functional interfaces in java.util.function derive from or specialize four foundational interface archetypes:
+-------------------------------------------------------------------------------+
| THE FOUR CORE FUNCTIONAL FAMILIES |
| |
| 1. Supplier<T> : () -> T [Produces data, takes nothing] |
| 2. Consumer<T> : (T) -> void [Consumes data, returns nothing] |
| 3. Predicate<T> : (T) -> boolean [Evaluates boolean condition] |
| 4. Function<T, R> : (T) -> R [Transforms input T to output R] |
+-------------------------------------------------------------------------------+
1. Supplier<T>
- SAM:
T get() - Purpose: Generates or supplies an instance of type
Twithout requiring any input parameters. - Common Use: Lazy evaluation, factory methods, default value generation.
Supplier<LocalDate> todaySupplier = () -> LocalDate.now();
Supplier<List<String>> listFactory = ArrayList::new;
LocalDate now = todaySupplier.get();
2. Consumer<T>
- SAM:
void accept(T t) - Purpose: Accepts a single input argument of type
Tand performs a side-effect operation, returning no result (void). - Default Method:
default Consumer<T> andThen(Consumer<? super T> after)executes the current consumer followed by theafterconsumer.
Consumer<String> printer = s -> System.out.print("[" + s + "]");
Consumer<String> logger = s -> System.out.println(" Logged: " + s.length());
Consumer<String> pipeline = printer.andThen(logger);
pipeline.accept("Java 21"); // Prints: [Java 21] Logged: 7
3. Predicate<T>
- SAM:
boolean test(T t) - Purpose: Evaluates a condition against an argument of type
T, returning a primitiveboolean. - Default Methods:
default Predicate<T> and(Predicate<? super T> other)(Logical AND with short-circuit evaluation)default Predicate<T> or(Predicate<? super T> other)(Logical OR with short-circuit evaluation)default Predicate<T> negate()(Logical NOT)
- Static Factory Methods:
static <T> Predicate<T> isEqual(Object targetRef)(UsesObjects.equals)static <T> Predicate<T> not(Predicate<? super T> target)(Java 11+ helper for method references)
Predicate<String> isNonEmpty = s -> s != null && !s.isBlank();
Predicate<String> isShort = s -> s.length() < 10;
Predicate<String> validShortName = isNonEmpty.and(isShort);
// Java 11+ Predicate.not with method references
List<String> items = List.of("A", "", "B", " ");
long count = items.stream().filter(Predicate.not(String::isBlank)).count(); // 2
4. Function<T, R>
- SAM:
R apply(T t) - Purpose: Accepts an argument of type
Tand computes a result of typeR. - Default Methods:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after): Evaluatesthisfirst, then passes the result toafter.default <V> Function<V, R> compose(Function<? super V, ? extends T> before): Evaluatesbeforefirst, then passes the result tothis.
- Static Method:
static <T> Function<T, T> identity()returns a function that always returns its input argument.
Function<String, Integer> parse = Integer::parseInt;
Function<Integer, Integer> square = x -> x * x;
// andThen: parse first, then square: parse(s) -> square(result)
Function<String, Integer> parseAndSquare = parse.andThen(square);
int res1 = parseAndSquare.apply("5"); // 25
// compose: square first, then parse: square(x) -> parse(result) - Requires compatible types
Function<Integer, Integer> multiplyByTwo = x -> x * 2;
Function<Integer, Integer> squareThenDouble = multiplyByTwo.compose(square);
int res2 = squareThenDouble.apply(3); // square(3)=9, multiplyByTwo(9)=18
3. Binary Functional Interfaces
When operations require two input arguments, Java provides binary functional interface variants. Notice that there is no BiSupplier in Java because a method cannot return multiple independent return values.
| Interface | SAM Signature | Return Type | Key Methods |
|---|---|---|---|
BiConsumer<T, U> | void accept(T t, U u) | void | andThen(BiConsumer) |
BiPredicate<T, U> | boolean test(T t, U u) | boolean | and(), or(), negate() |
BiFunction<T, U, R> | R apply(T t, U u) | R | andThen(Function) |
[!WARNING]
BiFunctionhas NOcompose()method: WhileFunction<T, R>provides bothcompose()andandThen(),BiFunction<T, U, R>provides onlyandThen(Function<? super R, ? extends V> after). It does not supportcompose()because a preceding function would need to produce two return values simultaneously to feedTandUintoBiFunction.
4. Operator Interfaces: UnaryOperator & BinaryOperator
Operators are specializations of Function and BiFunction where the input argument types and return type are identical.
// UnaryOperator<T> extends Function<T, T>
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
// BinaryOperator<T> extends BiFunction<T, T, T>
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
UnaryOperator<String> toUpper = String::toUpperCase;
String result = toUpper.apply("java 21"); // "JAVA 21"
BinaryOperator<Integer> adder = (a, b) -> a + b;
int sum = adder.apply(10, 20); // 30
BinaryOperator<String> longest = BinaryOperator.maxBy(Comparator.comparingInt(String::length));
String winner = longest.apply("Elephant", "Cat"); // "Elephant"
5. Primitive Functional Interface Specializations
To avoid the severe memory and CPU performance overhead of boxing and unboxing primitives (int $\leftrightarrow$ Integer, double $\leftrightarrow$ Double, long $\leftrightarrow$ Long), java.util.function includes specialized interfaces for the three primary numerical primitive types: int, long, and double (plus BooleanSupplier).
1. Primitive Suppliers, Consumers, and Predicates
| Interface | SAM Signature | Method Name |
|---|---|---|
IntSupplier | int getAsInt() | getAsInt() |
LongSupplier | long getAsLong() | getAsLong() |
DoubleSupplier | double getAsDouble() | getAsDouble() |
BooleanSupplier | boolean getAsBoolean() | getAsBoolean() |
IntConsumer | void accept(int value) | accept(int) |
LongConsumer | void accept(long value) | accept(long) |
DoubleConsumer | void accept(double value) | accept(double) |
IntPredicate | boolean test(int value) | test(int) |
LongPredicate | boolean test(long value) | test(long) |
DoublePredicate | boolean test(double value) | test(double) |
2. Primitive Function Categories
Category A: Takes Primitive, Returns Object (XFunction<R>)
IntFunction<R>:R apply(int value)LongFunction<R>:R apply(long value)DoubleFunction<R>:R apply(double value)
Category B: Takes Object, Returns Primitive (ToXFunction<T>)
ToIntFunction<T>:int applyAsInt(T value)ToLongFunction<T>:long applyAsLong(T value)ToDoubleFunction<T>:double applyAsDouble(T value)
Category C: Takes Primitive, Returns Primitive (XToYFunction - 9 combinations)
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)IntUnaryOperator:int applyAsInt(int operand)LongUnaryOperator:long applyAsLong(long operand)DoubleUnaryOperator:double applyAsDouble(double operand)
Category D: Two-Argument Primitive Variants
ObjIntConsumer<T>:void accept(T t, int value)ObjLongConsumer<T>:void accept(T t, long value)ObjDoubleConsumer<T>:void accept(T t, double value)ToIntBiFunction<T, U>:int applyAsInt(T t, U u)ToLongBiFunction<T, U>:long applyAsLong(T t, U u)ToDoubleBiFunction<T, U>:double applyAsDouble(T t, U u)IntBinaryOperator:int applyAsInt(int left, int right)LongBinaryOperator:long applyAsLong(long left, long right)DoubleBinaryOperator:double applyAsDouble(double left, double right)
// Primitive Specializations in Practice
IntToDoubleFunction half = x -> x / 2.0;
double halfResult = half.applyAsDouble(7); // 3.5
ObjIntConsumer<List<String>> listAppender = (list, index) -> list.add("Item #" + index);
List<String> storage = new ArrayList<>();
listAppender.accept(storage, 42); // storage contains ["Item #42"]
ToIntFunction<String> strLength = String::length;
int len = strLength.applyAsInt("Oracle Java 21"); // 14
6. Comprehensive Summary Table for the 1Z0-830 Exam
| Interface Name | Number of Args | SAM Method Name | Return Type | Boxed or Primitive |
|---|---|---|---|---|
Supplier<T> | 0 | get() | T | Reference |
BooleanSupplier | 0 | getAsBoolean() | boolean | Primitive |
IntSupplier | 0 | getAsInt() | int | Primitive |
Consumer<T> | 1 | accept(T t) | void | Reference |
IntConsumer | 1 | accept(int v) | void | Primitive |
BiConsumer<T, U> | 2 | accept(T t, U u) | void | Reference |
ObjIntConsumer<T> | 2 | accept(T t, int v) | void | Hybrid |
Predicate<T> | 1 | test(T t) | boolean | Reference input |
IntPredicate | 1 | test(int v) | boolean | Primitive input |
BiPredicate<T, U> | 2 | test(T t, U u) | boolean | Reference inputs |
Function<T, R> | 1 | apply(T t) | R | Reference |
IntFunction<R> | 1 | apply(int v) | R | Primitive input |
ToIntFunction<T> | 1 | applyAsInt(T t) | int | Primitive output |
IntToDoubleFunction | 1 | applyAsDouble(int v) | double | Primitive to Primitive |
BiFunction<T, U, R> | 2 | apply(T t, U u) | R | Reference |
ToIntBiFunction<T, U> | 2 | applyAsInt(T t, U u) | int | Primitive output |
UnaryOperator<T> | 1 | apply(T t) | T | Reference |
IntUnaryOperator | 1 | applyAsInt(int v) | int | Primitive |
BinaryOperator<T> | 2 | apply(T t1, T t2) | T | Reference |
IntBinaryOperator | 2 | applyAsInt(int a, int b) | int | Primitive |
Which of the following interfaces is a valid functional interface that compiles without error when annotated with @FunctionalInterface?
Given the following functional expressions: Function<Integer, Integer> multiplyByThree = x -> x * 3; Function<Integer, Integer> addFive = x -> x + 5; Function<Integer, Integer> combined = multiplyByThree.compose(addFive); int result = combined.apply(2); What is the resulting value of result?
Which of the following functional interfaces correctly maps an int primitive input directly to a double primitive output without boxing, and what is its Single Abstract Method name?
Examine the following code snippet utilizing java.util.function interfaces: BiPredicate<String, Integer> lengthChecker = (s, len) -> s.length() == len; BiPredicate<String, Integer> nonNull = (s, len) -> s != null; BiPredicate<String, Integer> validator = nonNull.and(lengthChecker.negate()); boolean res = validator.test("Java21", 6); What is the value of res?