W3docs

Java Function Interface

Transform a value of one type into another in Java with the Function interface and andThen/compose.

Java Function Interface

Function<T, R> is the functional interface for the question "turn this T into an R" — one input, one output, no side effects expected. It's the shape Stream.map takes, the shape Optional.map takes, the shape Map.computeIfAbsent takes, and the shape every JDK method that says "transform this into that" accepts. Single abstract method, three or four useful default methods, and a small algebra (andThen, compose, identity) for stringing transformations together without writing intermediate lambdas.

The interface

@FunctionalInterface
public interface Function<T, R> {
  R apply(T t);                                                    // the only abstract method

  default <V> Function<V, R> compose(Function<? super V, ? extends T> before);
  default <V> Function<T, V> andThen(Function<? super R, ? extends V> after);
  static  <T> Function<T, T> identity();
}

apply(T) is the SAM (single abstract method). Every lambda or method reference that ends up in a Function<T, R> position implements it.

Function<String, Integer> length = String::length;
int n = length.apply("hello");                  // 5

You'll usually let stream.map(length) or optional.map(length) call apply for you. Knowing the method name matters when you write code that accepts a Function<T, R> and needs to call it once.

andThen and compose — two ways to chain

The two default methods build a new Function by chaining the receiver with another. They differ only in direction:

Function<String, String>  trim     = String::trim;
Function<String, Integer> length   = String::length;

Function<String, Integer> trimThenLength = trim.andThen(length);     // f.andThen(g): g(f(x))
Function<String, Integer> sameThing      = length.compose(trim);     // g.compose(f): g(f(x))

Both build the same pipeline s -> length(trim(s)). The difference is which one reads better at the call site:

  • andThen reads left to right, in the same order the data flows. trim.andThen(length).andThen(asString) is "trim, then length, then asString."
  • compose reads right to left, the way mathematical composition is written: f ∘ g means "apply g first, then f." length.compose(trim) is "length after trim."

In application code andThen is almost always the clearer choice — code reads top-to-bottom, left-to-right, and a left-to-right pipeline matches that. compose is useful when you have a final function and want to prepend preprocessing without rewriting the chain.

Both are lazy in the sense that they don't run anything at composition time; they just produce a new Function whose apply calls the underlying ones in the right order.

Function.identity() — the no-op transformation

Function<T, T> id = Function.identity();      // t -> t

identity() returns the same instance every call (a singleton lambda), so it has zero allocation cost. The single place it earns its keep is as a key-or-value mapper in Collectors.toMap, where you need to pass a Function even when the value is "the element itself":

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

Without Function.identity() you'd write p -> p, which allocates a fresh lambda each call and reads worse.

A subtle point: identity() only works when the input and output types are the same. The moment a generic widens (Function<? super T, ? extends R>), the compiler may force you to spell out a lambda again. That's an edge case but worth knowing when generic inference complains.

Function<T, R> versus UnaryOperator<T>

UnaryOperator<T> is the specialisation for the case where input and output are the same type:

UnaryOperator<String> upper = String::toUpperCase;       // String -> String
Function<String, String> sameShape = String::toUpperCase;

Both are valid Function<String, String> instances — UnaryOperator<T> extends Function<T, T>. The difference is at API level: List.replaceAll, Map.replaceAll, and Comparator.thenComparing(UnaryOperator) declare UnaryOperator<T> because "replace every element with a transformed same-type value" is exactly that shape. Pass a method reference and the compiler picks the right one.

BiFunction<T, U, R> — two inputs

The two-argument shape:

BiFunction<String, Integer, String> repeat = String::repeat;
String s = repeat.apply("ab", 3);             // "ababab"

BiFunction has the same andThen but no compose — the asymmetry is by design, because preprocessing a two-arg function would mean two compose parameters.

The JDK uses BiFunction<K, V, V> for Map.merge and BiFunction<K, V, V_NEW> for Map.compute. BinaryOperator<T> is the special case where all three type parameters are T (input, input, and output all the same) — covered in the BinaryOperator chapter.

Primitive specialisations — three families

Function<Integer, String> boxes the int on every call. The package ships three families to avoid that:

// 1. Primitive in, object out — "IntFunction<R>"
IntFunction<String>     fromInt   = i -> "n=" + i;

// 2. Object in, primitive out — "ToIntFunction<T>"
ToIntFunction<String>   strLen    = String::length;
ToDoubleFunction<Item>  price     = Item::price;

// 3. Primitive in, primitive out — "IntToLongFunction", "IntUnaryOperator", etc.
IntToLongFunction       square    = i -> (long) i * i;
IntUnaryOperator        doubleIt  = i -> i * 2;
DoubleUnaryOperator     halve     = d -> d / 2.0;

The naming reads like a sentence:

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

Stream.mapToInt(ToIntFunction) is the bridge from a boxed Stream<T> into an IntStream. Once you're on an IntStream, every transform uses IntUnaryOperator or IntToLongFunction — and the boxing tax stays at zero.

A worked example: composition, identity, and a primitive specialisation

The program below builds two Functions, composes them with both andThen and compose to show they're equivalent, uses Function.identity() inside a Collectors.toMap, and contrasts a boxed Function<Integer, Integer> with a primitive IntUnaryOperator on a workload large enough to feel the boxing tax.

java— editable, runs on the server

What to take from the run:

  • trim.andThen(upper) and upper.compose(trim) produced the same String from the same input. They differ only in which name reads naturally where you write it — andThen matches left-to-right data flow, compose matches mathematical "f after g" notation.
  • The longer chain trim.andThen(upper).andThen(length) changed the output type from String to Integer along the way. The pipeline composes type-safely; the compiler tracked String -> String -> String -> Integer for you.
  • Function.identity() slotted into Collectors.toMap(Person::name, Function.identity()) as the value mapper. The lambda p -> p would have worked, but identity() is the singleton no-allocation form and reads as the intent ("the value is the person").
  • The boxed Function<Integer, Integer> ran meaningfully slower than the primitive IntUnaryOperator — every call was paying for two Integer allocations. In hot pipelines that touch millions of values, the primitive variant is the version that earns its keep.
  • BiFunction.andThen(Function) chained a two-argument function with a one-argument follow-up. There is no BiFunction.compose — preprocessing two inputs would need two compose arguments, which the API deliberately avoids.

What's next

Function<T, R> and Predicate<T> are both pure shapes — input, output, no expected side effects. The next chapter, Java Consumer and Supplier, covers the two interfaces that step outside that purity: Consumer<T> takes an input and produces nothing (a side effect — print, log, store), and Supplier<T> takes nothing and produces an output (lazy default, factory, randomness). They round out the four-corner taxonomy you saw in the built-in interfaces overview.

Practice

Practice

You have `Function<String, String> trim = String::trim;` and `Function<String, Integer> length = String::length;`. You want a `Function<String, Integer>` that trims first and then takes the length. Which expression builds it most naturally?