Working with Built-in Functional Interfaces
OCP Exam Topic — Use built-in functional interfaces from
java.util.function. Covered in Chapter 8 of OCP Java SE 21 Developer Study Guide (Selikoff & Boyarsky).
Overview
The java.util.function package provides a standard library of functional interfaces that cover the most common patterns. Knowing their method signatures and return types is essential for the exam.
Core Single-Type Interfaces
Predicate<T>
Tests a condition on an object. Returns boolean.
- Abstract method:
boolean test(T t) - Key defaults:
and,or,negate
javaimport java.util.function.Predicate;
Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> startsA = s -> s.startsWith("A");
System.out.println(isLong.test("Java")); // false
System.out.println(isLong.and(startsA).test("Algorithms")); // true
System.out.println(isLong.negate().test("Hi")); // trueConsumer<T>
Accepts one argument and returns nothing. Typically used for side effects.
- Abstract method:
void accept(T t) - Key default:
andThen
javaimport java.util.function.Consumer;
Consumer<String> print = System.out::println;
Consumer<String> shout = s -> System.out.println(s.toUpperCase());
Consumer<String> printThenShout = print.andThen(shout);
printThenShout.accept("hello"); // prints "hello" then "HELLO"Supplier<T>
Produces a value without taking any input. Useful for lazy evaluation and factories.
- Abstract method:
T get()
javaimport java.util.function.Supplier;
Supplier<Double> random = Math::random;
System.out.println(random.get()); // some double between 0.0 and 1.0Function<T, R>
Transforms an input of type T into an output of type R.
- Abstract method:
R apply(T t) - Key defaults:
andThen,compose - Key static:
identity()
javaimport java.util.function.Function;
Function<String, Integer> length = String::length;
Function<Integer, String> toHex = Integer::toHexString;
Function<String, String> pipeline = length.andThen(toHex);
System.out.println(pipeline.apply("Hello")); // "5"UnaryOperator<T>
A specialisation of Function<T, T> where input and output are the same type.
- Abstract method:
T apply(T t)(inherited fromFunction)
javaimport java.util.function.UnaryOperator;
UnaryOperator<String> trim = String::strip;
System.out.println(trim.apply(" hello ")); // "hello"BinaryOperator<T>
A specialisation of BiFunction<T, T, T> where both inputs and the output share the same type.
- Abstract method:
T apply(T t1, T t2)(inherited fromBiFunction)
javaimport java.util.function.BinaryOperator;
BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
System.out.println(max.apply(7, 3)); // 7Bi- Variants
These accept two input parameters.
| Interface | Abstract Method | Description |
|---|---|---|
BiPredicate<T, U> |
boolean test(T t, U u) |
Test with two inputs |
BiConsumer<T, U> |
void accept(T t, U u) |
Consume two inputs, no return |
BiFunction<T, U, R> |
R apply(T t, U u) |
Transform two inputs to output |
javaimport java.util.function.*;
BiPredicate<String, Integer> longerThan = (s, n) -> s.length() > n;
System.out.println(longerThan.test("Java", 3)); // true
BiConsumer<String, Integer> print = (s, n) -> System.out.println(s.repeat(n));
print.accept("ha", 3); // "hahaha"
BiFunction<String, String, String> concat = (a, b) -> a + b;
System.out.println(concat.apply("Hello, ", "World!")); // "Hello, World!"Quick Reference Table
| Interface | Method | Input | Output |
|---|---|---|---|
Predicate<T> |
test(T) |
T | boolean |
Consumer<T> |
accept(T) |
T | void |
Supplier<T> |
get() |
— | T |
Function<T,R> |
apply(T) |
T | R |
UnaryOperator<T> |
apply(T) |
T | T |
BinaryOperator<T> |
apply(T,T) |
T, T | T |
BiPredicate<T,U> |
test(T,U) |
T, U | boolean |
BiConsumer<T,U> |
accept(T,U) |
T, U | void |
BiFunction<T,U,R> |
apply(T,U) |
T, U | R |
Primitive Specialisations
Using generic interfaces like Function<Integer, Boolean> boxes and unboxes primitives on every call, which harms performance. Java provides primitive variants to avoid this.
Predicate Variants
| Interface | Method |
|---|---|
IntPredicate |
boolean test(int value) |
LongPredicate |
boolean test(long value) |
DoublePredicate |
boolean test(double value) |
javaimport java.util.function.IntPredicate;
IntPredicate isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // falseFunction Variants (selected)
| Interface | Method | Description |
|---|---|---|
IntFunction<R> |
R apply(int value) |
int → R |
LongFunction<R> |
R apply(long value) |
long → R |
DoubleFunction<R> |
R apply(double value) |
double → R |
ToIntFunction<T> |
int applyAsInt(T value) |
T → int |
ToLongFunction<T> |
long applyAsLong(T value) |
T → long |
ToDoubleFunction<T> |
double applyAsDouble(T value) |
T → double |
IntUnaryOperator |
int applyAsInt(int) |
int → int |
IntBinaryOperator |
int applyAsInt(int, int) |
int, int → int |
javaimport java.util.function.*;
ToIntFunction<String> length = String::length;
System.out.println(length.applyAsInt("OCP")); // 3
IntBinaryOperator add = (a, b) -> a + b;
System.out.println(add.applyAsInt(3, 4)); // 7Key Points to Remember
Predicatetests;Consumerconsumes;Suppliersupplies;Functiontransforms.UnaryOperatorextendsFunctionfor same-type transformations;BinaryOperatorextendsBiFunction.Bivariants accept two parameters instead of one.- Primitive specialisations (
IntPredicate,LongFunction,ToIntFunction, etc.) avoid autoboxing overhead. - Memorise which abstract method name each interface uses:
test,accept,get,apply.