W3docs

Java Built-in Functional Interfaces

The java.util.function package — Function, Predicate, Consumer, Supplier, and their specialized variants.

Java Built-in Functional Interfaces

The java.util.function package shipped with Java 8 to give the JDK — and your code — a shared vocabulary for lambdas. Without it, every method that accepted a function would have to define its own one-off interface (StringMapper, IntToBool, RowHandler, …), and lambdas defined for one couldn't be reused for another. The package solves that with 43 small interfaces that cover the shapes that come up over and over: "take one thing, return another," "take a thing, decide yes or no," "take a thing, do something," "give me a thing."

If you only learn four interfaces from this package, learn Function, Predicate, Consumer, and Supplier. Almost everything else is a variant of one of them — two-argument versions, primitive specialisations to avoid boxing, or composition helpers.

The four big ones

Function<T, R>  f = t -> ...;       // takes a T, returns an R          — r = f.apply(t)
Predicate<T>    p = t -> ...;       // takes a T, returns a boolean      — boolean b = p.test(t)
Consumer<T>     c = t -> { ... };   // takes a T, returns nothing        — c.accept(t)
Supplier<T>     s = () -> ...;      // takes nothing, returns a T        — t = s.get()

Each one is @FunctionalInterface-annotated and has a one-word abstract method (apply, test, accept, get). You'll rarely call those methods directly when streams are involved — stream().filter(predicate).map(function).forEach(consumer) does the calling for you — but knowing the method name matters when you write code that takes a Function<T, R> as a parameter and needs to invoke it.

The shapes map onto common questions:

QuestionInterface
"Transform an X into a Y?"Function<X, Y>
"Is this X good?"Predicate<X>
"Do something with this X"Consumer<X>
"Give me an X"Supplier<X>

Two-argument variants

When the operation needs two inputs, add the Bi-prefix:

BiFunction<T, U, R>     f = (t, u) -> ...;     // two ins, one out                    — apply
BiPredicate<T, U>       p = (t, u) -> ...;     // two ins, a boolean                  — test
BiConsumer<T, U>        c = (t, u) -> { ... }; // two ins, no out                     — accept

There is no BiSupplierSupplier takes zero arguments by definition, so a "two-argument supplier" would just be a BiFunction.

The Bi-variants are exactly what Map.forEach((k, v) -> ...), Map.merge, and Map.compute expect:

Map<String, Integer> scores = new HashMap<>();
scores.forEach((name, score) -> System.out.println(name + "=" + score));   // BiConsumer
scores.merge("alice", 1, Integer::sum);                                       // BinaryOperator<Integer>

BinaryOperator<T> is a BiFunction<T, T, T> — same type for both inputs and the output. UnaryOperator<T> is similarly a Function<T, T>.

Primitive specialisations — avoiding the boxing tax

Function<Integer, Integer> works, but every call boxes the input and boxes the result. In a tight loop that's a real cost. The package therefore offers primitive-specialised versions:

IntFunction<R>           f = i -> ...;        // int in, R out
IntPredicate             p = i -> ...;        // int in, boolean out
IntConsumer              c = i -> { ... };    // int in, void
IntSupplier              s = () -> 42;        // void in, int out
IntUnaryOperator         u = i -> i * 2;      // int in, int out
IntBinaryOperator        b = (a, c2) -> a + c2;

ToIntFunction<T>         f1 = t -> t.hashCode();         // T in, int out
ToIntBiFunction<T, U>    f2 = (t, u) -> t.hashCode() + u.hashCode();

IntToLongFunction        f3 = i -> (long) i * i;          // int in, long out
IntToDoubleFunction      f4 = i -> Math.sqrt(i);

The same family exists for Long and Double. The naming convention reads like a sentence:

  • IntX — operates on an int.
  • ToIntX — produces an int.
  • IntToLongXint in, long out.

In stream code, mapToInt(...) returns an IntStream, whose terminal operations (sum, average, min, max) all return primitives without boxing — which is one of the largest practical wins of the primitive variants.

Composition built into the interfaces

Most of the interfaces come with default methods that let you compose without writing new lambdas:

// Function: andThen (left-to-right), compose (right-to-left)
Function<String, String>  trim  = String::trim;
Function<String, Integer> len   = String::length;
Function<String, Integer> trimLen = trim.andThen(len);          // trim, then length
Function<String, Integer> sameThing = len.compose(trim);          // length applied after trim

// Predicate: and / or / negate
Predicate<String> notNull  = Objects::nonNull;
Predicate<String> notBlank = s -> !s.trim().isEmpty();
Predicate<String> useful   = notNull.and(notBlank);
Predicate<String> blank    = notBlank.negate();

// Consumer: andThen (run two consumers in sequence)
Consumer<String> log   = System.out::println;
Consumer<String> save  = s -> writeToFile(s);
Consumer<String> both  = log.andThen(save);

// Comparator (in java.util, not java.util.function, but the same idea):
Comparator<Person> byName = Comparator.comparing(Person::name);
Comparator<Person> ordered = byName.thenComparing(Person::age);

There's also a useful static factory: Predicate.not(p) is shorthand for p.negate() and reads more naturally at a call site:

list.removeIf(Predicate.not(String::isBlank));    // remove all blank strings

Function.identity and Predicate.isEqual — the small useful statics

Two factory methods you'll see in stream code and should recognise:

Function<T, T> id = Function.identity();          // t -> t — useful as a no-op map

Predicate<Object> isFoo = Predicate.isEqual(\"foo\");  // o -> Objects.equals(o, \"foo\")

Function.identity() is most often used as a key or value mapper in Collectors.toMap:

Map<String, Person> byName = people.stream()
    .collect(Collectors.toMap(Person::name, Function.identity()));

Predicate.isEqual is rarely shorter than s -> s.equals("foo"), but it null-safely compares with Objects.equals, which matters when the stream may contain null.

A worked example: the four big ones, composition, and primitive specialisation

The program below uses Function, Predicate, Consumer, and Supplier, composes a couple of them, and contrasts a Function<Integer, Integer> (boxed) with a IntUnaryOperator (primitive) by summing a small list.

java— editable, runs on the server

What to take from the run:

  • The four big interfaces map cleanly onto four kinds of work: transform (Function), test (Predicate), act (Consumer), produce (Supplier). Their abstract-method names (apply, test, accept, get) are worth memorising.
  • trim.andThen(length) and notNull.and(notBlank) built new values from old ones with no helper method declarations. That's the composition algebra the interfaces carry as default methods.
  • The boxed Function<Integer, Integer> is meaningfully slower than the primitive IntUnaryOperator because every call allocates two Integer objects. In hot paths — stream pipelines that touch millions of values — the primitive specialisations earn their keep.
  • Predicate.not(notBlank) reads more naturally than notBlank.negate() at a removeIf call site. Both compile to the same thing.

What's next

You've now seen the standard vocabulary. The remaining lambda ergonomics question is "when the lambda body just delegates to one existing method, can I write it shorter?" Yes — with method references. The next chapter, Java Method References, covers the :: operator and its four forms (static, bound instance, unbound instance, constructor), and explains when a method reference is clearer than a lambda and when it's the opposite.

Practice

Practice

A method declares `void each(Consumer<String> action)`. Which of the following are valid arguments?