Function & BiFunction
Function & BiFunction
In the previous lesson you worked with Predicate, which always returns a boolean. The Function interface generalises that idea: it takes a value of one type and transforms it into a value of a potentially different type. This is the interface behind virtually every data-mapping, parsing, and conversion step you write with lambdas.
The Function interface
java.util.function.Function<T, R> has one abstract method:
T is the input type; R is the result type. They can be the same, but they do not have to be. A few quick examples:
Function when you want to pass a transformation as a parameter, store it in a variable, or combine it with other functions. If you just need to call the logic once in place, a regular method is simpler.
Composing with andThen
Function has a default method andThen(Function after) that chains two functions together: this function runs first, then after receives its result.
You can chain as many functions as you like:
f.andThen(g) means "do f, then pass its output to g". The data flows left to right, matching how you would read a sentence. This makes multi-step transformations very readable.
Composing with compose
compose(Function before) is the mirror of andThen: before runs first, and this function receives its result.
f.andThen(g) means f → g. f.compose(g) means g → f (g goes first). In practice most developers reach for andThen because the left-to-right reading order matches the execution order. Reserve compose for cases where you are decorating an existing function from the outside.
The BiFunction interface
Sometimes a transformation needs two inputs. java.util.function.BiFunction<T, U, R> covers that:
A practical example — formatting a full name from separate first and last name strings:
Another example — combining a label with a numeric value for display:
BiFunction also has andThen (but NOT compose), so you can post-process the result:
UnaryOperator and BinaryOperator — convenient specialisations
When the input and output types are the same, Java provides two shorthand interfaces:
UnaryOperator<T>extendsFunction<T, T>— one argument, same return type.BinaryOperator<T>extendsBiFunction<T, T, T>— two arguments, same return type.
Use these instead of Function<T, T> or BiFunction<T, T, T> — they are more expressive and certain APIs (like List.replaceAll) expect them by type.
Putting it together — a reusable transformation pipeline
Here is a realistic mini-example: a simple data normalisation pipeline built from composable Function instances.
Notice that each step is a named variable, making the pipeline self-documenting. You can reuse trim, lower, and slug independently in other pipelines — this is the composability payoff.
Summary
Function<T, R>transforms a value of typeTinto a value of typeRviaapply.andThen(g)chains: this function first, theng(left-to-right).compose(g)chains:gfirst, then this function (right-to-left).BiFunction<T, U, R>accepts two arguments and returns one result.UnaryOperator<T>andBinaryOperator<T>are shorthand when types are uniform.