Composing functions in Java
andThen, compose, Predicate.and/or/negate, Function.identity.
andThen vs compose
Functional interfaces have default methods that build bigger functions. **f.andThen(g) runs f first, then g on the result: g(f(x)). f.compose(g) runs g first**, then f: f(g(x)).
Function<Integer, Integer> plus2 = x -> x + 2;
Function<Integer, Integer> times3 = x -> x * 3;
plus2.andThen(times3).apply(1); // (1+2)*3 = 9
plus2.compose(times3).apply(1); // 1*3+2 = 5Your turn
What does this print?
Function<Integer, Integer> inc = x -> x + 1;
Function<Integer, Integer> dbl = x -> x * 2;
System.out.println(inc.andThen(dbl).apply(5));
System.out.println(inc.compose(dbl).apply(5));12 1111 1212 12
Show the answer
andThen: inc first, (5 + 1) * 2 = 12. compose: dbl runs first, 5 * 2 + 1 = 11.
Predicates combine too
**p.and(q), p.or(q) and p.negate() build new predicates. Java 11 added Predicate.not(p)**, handy with method references.
Predicate<String> shortW = s -> s.length() < 4;
Predicate<String> hasE = s -> s.contains("e");
var p = shortW.and(hasE.negate());
// short AND has no "e"
var notBlank = Predicate.not(String::isBlank);Short words without e
What does this print?
Predicate<String> shortW = s -> s.length() < 4;
Predicate<String> hasE = s -> s.contains("e");
var p = shortW.and(hasE.negate());
System.out.println(p.test("cat"));
System.out.println(p.test("bee"));true falsefalse truetrue true
Show the answer
"cat" is short and has no e → true. "bee" is short but contains e → false.
negate() flips the whole chain
negate() applies to everything built before it. pos.or(even).negate() means "neither positive nor even", which is very different from pos.or(even.negate()).
var p = pos.or(even).negate();
// -2 → false (even)
// -1 → true (neither)
// 3 → false (positive)identity(): the do-nothing function
**Function.identity()** returns its input unchanged (x -> x). It's the perfect starting point when you build a pipeline step by step.
Function<String, String> pipeline =
Function.identity();
for (var step : steps) {
pipeline = pipeline.andThen(step);
}Pipelines from parts
Validation rules (notNull.and(notBlank).and(maxLength)), text-cleaning pipelines and pricing rules are often built by composing small, separately tested functions. Each piece stays tiny; the combination does the real work.
Key takeaways
- f.andThen(g).apply(x) = g(f(x))
- f.compose(g).apply(x) = f(g(x))
- Predicate: p.and(q), p.or(q), p.negate(), Predicate.not(p)
- Function.identity() is x -> x
💡 Composing functions is like connecting pipes: water flows through each section in the order you join them.
Predicate.not was added in Java 11 for one very common case: writing filter(Predicate.not(String::isBlank)) instead of the clumsier filter(s -> !s.isBlank()).
Practice questions
What does this print?
Function<Integer, Integer> plus2 = x -> x + 2;
Function<Integer, Integer> times3 = x -> x * 3;
System.out.println(plus2.andThen(times3).apply(1));
System.out.println(plus2.compose(times3).apply(1));- 9 5
- 5 9
- 9 9
- 5 5
Check your answer
9 5. andThen: (1 + 2) * 3 = 9. compose runs times3 first: 1 * 3 + 2 = 5.
What does this print?
Predicate<String> longWord = s -> s.length() > 3;
Predicate<String> startsA = s -> s.startsWith("a");
var p = longWord.and(startsA.negate());
System.out.println(p.test("apple"));
System.out.println(p.test("bear"));- true false
- false true
- true true
- false false
Check your answer
false true. p means 'longer than 3 AND does not start with a'. "apple" starts with a → false. "bear" is long and doesn't start with a → true.