λ Lambdas & Functional Java · Intermediate

Composing functions in Java

andThen, compose, Predicate.and/or/negate, Function.identity.

🧩 The mysteryplus2 then times3 gives 9. Swap one word, compose instead of andThen, and the same pieces give 5. Order matters, and Java gives you both.

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 = 5
🔮 Predict it

Your 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));
  1. 12 11
  2. 11 12
  3. 12 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);
🔮 Predict it

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"));
  1. true false
  2. false true
  3. true true
Show the answer

"cat" is short and has no e → true. "bee" is short but contains e → false.

⚠️ The trap

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);
}
💼 In the real world

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

  1. f.andThen(g).apply(x) = g(f(x))
  2. f.compose(g).apply(x) = f(g(x))
  3. Predicate: p.and(q), p.or(q), p.negate(), Predicate.not(p)
  4. Function.identity() is x -> x

💡 Composing functions is like connecting pipes: water flows through each section in the order you join them.

🤯 Did you know?

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));
  1. 9 5
  2. 5 9
  3. 9 9
  4. 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"));
  1. true false
  2. false true
  3. true true
  4. 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.

Next: null has caused more crashes than any other value. Optional is Java's way of saying "there might be nothing here" out loud.