Cheat SheetsJava A–ZStreams & Functional Java

Streams & Functional Java — Cheat Sheet

Java A–Z · 10 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Streams & Functional Java
Java A–Z10 topicsQuick revision reference
1

Lambda Expressions

  • Lambda syntax: (params) -> expr or (params) -> { body; return val; }
  • Parameter types and parentheses (single param) can be omitted — compiler infers them
  • Captured local variables must be final or effectively final
  • Inside a lambda, this refers to the enclosing class, not the lambda itself
  • Lambdas are compiled to invokedynamic — lighter than anonymous inner classes
  • A lambda can only implement a functional interface (exactly one abstract method)
LambdaSyntax.java
import java.util.*;
import java.util.function.*;

public class LambdaSyntax {
    public static void main(String[] args) {
        // Zero parameters
        Runnable r = () -> System.out.println("Running");
        r.run();

        // One parameter — parentheses optional
        Consumer<String> print = s -> System.out.println(s.toUpperCase());
        print.accept("hello");  // HELLO

        // Two parameters
        Comparator<Integer> cmp = (a, b) -> a - b;
        System.out.println(cmp.compare(3, 5)); // -2

        // Block body — multiple statements, explicit return
        Function<Integer, String> grade = score -> {
            if (score >= 90) return "A";
            if (score >= 80) return "B";
            return "C";
        };
        System.out.println(grade.apply(85)); // B

        // Lambdas as arguments
        List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob"));
        names.sort((a, b) -> a.compareTo(b));
        System.out.println(names); // [Alice, Bob, Charlie]

        names.forEach(name -> System.out.print(name + " "));
        System.out.println();
    }
}
2

Functional Interfaces

  • Four core types: Predicate (test), Function (transform), Consumer (side-effect), Supplier (produce)
  • Composition: Predicate.and/or/negate; Function.andThen/compose; Consumer.andThen
  • Use primitive specialisations (IntPredicate, ToIntFunction…) to avoid boxing overhead
  • BiXxx variants accept two inputs; UnaryOperator/BinaryOperator are same-type Function specialisations
  • @FunctionalInterface is optional but enables compile-time enforcement of the SAM constraint
  • Method references (::) are syntactic sugar for lambdas implementing matching functional interfaces
CoreFunctional.java
import java.util.function.*;
import java.util.List;

public class CoreFunctional {
    public static void main(String[] args) {
        // Predicate<T> — test a condition
        Predicate<String> isLong   = s -> s.length() > 5;
        Predicate<String> startsA  = s -> s.startsWith("A");
        Predicate<String> both     = isLong.and(startsA);
        Predicate<String> either   = isLong.or(startsA);
        Predicate<String> notLong  = isLong.negate();

        System.out.println(both.test("Avocado"));   // true
        System.out.println(both.test("Ant"));       // false

        // Function<T,R> — transform a value
        Function<String, Integer> length = String::length;
        Function<String, String>  upper  = String::toUpperCase;
        Function<String, String>  pipeline = upper.andThen(s -> s + "!");

        System.out.println(pipeline.apply("hello")); // HELLO!

        // compose: g.compose(f) = g(f(x)); andThen: f.andThen(g) = g(f(x))
        Function<Integer, Integer> times2  = x -> x * 2;
        Function<Integer, Integer> plus3   = x -> x + 3;
        System.out.println(times2.andThen(plus3).apply(5)); // 13 = (5*2)+3
        System.out.println(times2.compose(plus3).apply(5)); // 16 = (5+3)*2

        // Consumer<T> — side effect, returns void
        Consumer<String> logger  = s -> System.out.println("[LOG] " + s);
        Consumer<String> saver   = s -> System.out.println("[SAVE] " + s);
        Consumer<String> both2   = logger.andThen(saver);
        both2.accept("event");

        // Supplier<T> — produce a value
        Supplier<List<String>> listFactory = java.util.ArrayList::new;
        List<String> l = listFactory.get();
        l.add("item");
        System.out.println(l);
    }
}
3

Streams API

  • Streams are lazy — intermediate operations run only when a terminal operation is called
  • A stream cannot be reused after a terminal operation; create a new one from the source
  • flatMap flattens Stream<Stream<T>> or Stream<List<T>> into a single Stream<T>
  • Use mapToInt/mapToLong/mapToDouble + sum/average to avoid boxing overhead on numeric operations
  • Parallel streams use ForkJoinPool — avoid when operations have side effects or shared state
  • Stream.iterate() + limit() is the clean way to generate finite sequences
StreamIntermediate.java
import java.util.*;
import java.util.stream.*;

public class StreamIntermediate {
    public static void main(String[] args) {
        List<String> words = List.of("hello","world","java","streams","api","java");

        // filter + distinct + sorted + limit
        List<String> result = words.stream()
            .filter(w -> w.length() > 3)    // hello, world, java, streams, java
            .distinct()                      // hello, world, java, streams
            .sorted()                        // hello, java, streams, world
            .limit(3)                        // hello, java, streams
            .collect(Collectors.toList());
        System.out.println(result);

        // map — transform each element
        List<Integer> lengths = words.stream()
            .map(String::length)
            .distinct()
            .sorted()
            .collect(Collectors.toList());
        System.out.println(lengths); // [3, 4, 5, 7]

        // flatMap — flatten List<List<T>> to Stream<T>
        List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4), List.of(5));
        List<Integer> flat = nested.stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toList());
        System.out.println(flat); // [1, 2, 3, 4, 5]

        // peek — debug intermediate values (doesn't consume)
        long count = words.stream()
            .peek(w -> System.out.print("before: " + w + " "))
            .filter(w -> w.startsWith("j"))
            .peek(w -> System.out.print("after: " + w + " "))
            .count();
        System.out.println("
Count: " + count); // 2
    }
}
4

Stream Collectors

  • groupingBy produces Map<K, List<V>>; add a downstream collector to transform the values
  • partitioningBy always produces Map<Boolean, List<T>> — both true and false keys exist
  • joining(delimiter, prefix, suffix) is the clean way to build CSV/bracketed strings
  • toMap with three args (key, value, mergeFunction) handles duplicate keys gracefully
  • teeing (Java 12) runs two collectors in one pass — great for simultaneous min/max
  • summarizingInt/Long/Double returns count, sum, min, max, and average in one collector
BasicCollectors.java
import java.util.*;
import java.util.stream.*;

public class BasicCollectors {
    record Person(String name, String city, int age) {}

    public static void main(String[] args) {
        List<Person> people = List.of(
            new Person("Alice", "NYC", 30),
            new Person("Bob",   "LA",  25),
            new Person("Carol", "NYC", 35),
            new Person("Dave",  "LA",  28)
        );

        // toList (Java 16 shorthand), toSet, toUnmodifiableList
        List<String> names = people.stream()
            .map(Person::name)
            .collect(Collectors.toList());
        System.out.println(names);

        // toMap — key must be unique or merge function required
        Map<String, Integer> nameToAge = people.stream()
            .collect(Collectors.toMap(Person::name, Person::age));
        System.out.println(nameToAge);

        // toMap with merge function for duplicate keys
        Map<String, Long> cityCount = people.stream()
            .collect(Collectors.toMap(
                Person::city,
                p -> 1L,
                Long::sum));
        System.out.println(cityCount); // {NYC=2, LA=2}

        // joining — concatenate strings
        String csv = people.stream()
            .map(Person::name)
            .collect(Collectors.joining(", ", "[", "]"));
        System.out.println(csv); // [Alice, Bob, Carol, Dave]

        // counting, summingInt, averagingInt
        long total = people.stream().collect(Collectors.counting());
        int  sumAge = people.stream().collect(Collectors.summingInt(Person::age));
        double avg  = people.stream().collect(Collectors.averagingInt(Person::age));
        System.out.println(total + " | " + sumAge + " | " + avg); // 4 | 118 | 29.5
    }
}
5

Optional

  • Optional.of() throws NPE on null; ofNullable() handles null; empty() is explicitly absent
  • Prefer orElseGet(Supplier) over orElse(value) — the supplier is lazy, value is always evaluated
  • Use map/flatMap/filter to chain Optional operations without if-else null checks
  • flatMap prevents Optional<Optional<T>> when the mapper itself returns Optional
  • Optional.stream() (Java 9+) bridges Optional into Stream pipelines cleanly
  • Never use Optional as a field, parameter, or collection element — only as a return type
OptionalBasics.java
import java.util.Optional;

public class OptionalBasics {
    static Optional<String> findUserEmail(int id) {
        if (id == 1) return Optional.of("alice@example.com");
        if (id == 2) return Optional.ofNullable(null); // same as Optional.empty()
        return Optional.empty();
    }

    public static void main(String[] args) {
        Optional<String> email = findUserEmail(1);

        // isPresent / get — verbose, avoid get() without check
        if (email.isPresent()) System.out.println(email.get()); // alice@example.com

        // orElse — return default if empty
        System.out.println(findUserEmail(2).orElse("no-reply@example.com"));

        // orElseGet — lazy supplier (only called if empty)
        System.out.println(findUserEmail(99).orElseGet(() -> "generated@example.com"));

        // orElseThrow — throw if empty
        try {
            findUserEmail(99).orElseThrow(() ->
                new IllegalArgumentException("User not found"));
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage()); // User not found
        }

        // ifPresent — side-effect only if present
        findUserEmail(1).ifPresent(e -> System.out.println("Sending to: " + e));

        // ifPresentOrElse (Java 9+)
        findUserEmail(99).ifPresentOrElse(
            e  -> System.out.println("Email: " + e),
            () -> System.out.println("No email found"));
    }
}
6

Method References

  • Four forms: Class::staticMethod, instance::method, Class::instanceMethod, Class::new
  • Unbound instance ref (String::toUpperCase) takes the receiver as the first lambda argument
  • Constructor ref (ArrayList::new) is equivalent to () -> new ArrayList<>()
  • System.out::println is a bound instance reference — System.out is the fixed receiver
  • Prefer method references when the name communicates intent clearly; use lambdas for complex logic
  • Predicate.not(method::ref) (Java 11+) negates a method reference cleanly
MethodRefForms.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;

public class MethodRefForms {
    static int doubleIt(int n) { return n * 2; }

    public static void main(String[] args) {
        // 1. Static method reference
        Function<String, Integer> parser = Integer::parseInt;
        System.out.println(parser.apply("42")); // 42

        IntUnaryOperator dbl = MethodRefForms::doubleIt;
        System.out.println(dbl.applyAsInt(5)); // 10

        // 2. Bound instance — object is fixed
        String prefix = "Hello";
        Predicate<String> startsWithHello = prefix::startsWith; // fixed receiver
        System.out.println(startsWithHello.test("Hello World")); // true

        // 3. Unbound instance — object is first lambda arg
        Function<String, String> upper = String::toUpperCase;
        Comparator<String> cmp = String::compareTo;
        System.out.println(upper.apply("java")); // JAVA

        // 4. Constructor reference
        Supplier<List<String>> listFactory = ArrayList::new;
        Function<Integer, int[]> arrFactory = int[]::new;
        List<String> list = listFactory.get();
        int[] arr = arrFactory.apply(5);
        System.out.println(arr.length); // 5

        // Practical stream pipeline with method refs
        List<String> nums = List.of("3", "1", "4", "1", "5");
        List<Integer> sorted = nums.stream()
            .map(Integer::parseInt)          // static
            .sorted(Integer::compareTo)      // unbound
            .collect(Collectors.toList());
        System.out.println(sorted); // [1, 1, 3, 4, 5]

        // Constructor ref in stream
        List<StringBuilder> sbs = List.of("a","b","c").stream()
            .map(StringBuilder::new)         // constructor ref
            .collect(Collectors.toList());
        sbs.forEach(sb -> sb.append("!"));
        System.out.println(sbs); // [a!, b!, c!]
    }
}
7

Comparable & Comparator

  • compareTo: negative if this < other, 0 if equal, positive if this > other
  • Never use a - b in compareTo — integer overflow; use Integer.compare(a, b)
  • compareTo must be consistent with equals; inconsistency breaks TreeSet/TreeMap
  • Comparator.comparing(keyFn).thenComparing(...) builds multi-level sorts cleanly
  • reversed() flips the entire chain — attach it to individual steps to flip only one level
  • nullsFirst / nullsLast wrap any Comparator to handle null keys without NPE
ComparableDemo.java
import java.util.*;

public class ComparableDemo implements Comparable<ComparableDemo> {
    private final String name;
    private final int    priority;

    public ComparableDemo(String name, int priority) {
        this.name = name; this.priority = priority;
    }

    @Override
    public int compareTo(ComparableDemo other) {
        // Primary: priority ascending
        int cmp = Integer.compare(this.priority, other.priority);
        if (cmp != 0) return cmp;
        // Secondary: name alphabetically
        return this.name.compareTo(other.name);
    }

    @Override public String toString() { return name + "(" + priority + ")"; }

    public static void main(String[] args) {
        List<ComparableDemo> tasks = new ArrayList<>(List.of(
            new ComparableDemo("Deploy",  2),
            new ComparableDemo("Test",    1),
            new ComparableDemo("Build",   1),
            new ComparableDemo("Review",  2)
        ));
        Collections.sort(tasks); // uses compareTo
        System.out.println(tasks); // [Build(1), Test(1), Deploy(2), Review(2)]

        TreeSet<ComparableDemo> set = new TreeSet<>(tasks);
        System.out.println(set.first()); // Build(1)
    }
}
8

Iterator & Iterable

  • Implement Iterable<T> with iterator() to enable enhanced for-each on custom classes
  • Iterator.remove() is the only safe way to remove during iteration — do not use collection.remove() in a loop
  • removeIf(Predicate) is the cleanest bulk-removal approach — no CME risk
  • Fail-fast iterators throw ConcurrentModificationException on concurrent structural modification
  • ListIterator supports bidirectional traversal, set() and add() during iteration
  • StreamSupport.stream(iterable.spliterator(), parallel) converts any Iterable to a Stream
Range.java
import java.util.Iterator;
import java.util.NoSuchElementException;

// Custom range that is Iterable — enables for-each
public class Range implements Iterable<Integer> {
    private final int start;
    private final int end;   // exclusive

    public Range(int start, int end) {
        this.start = start;
        this.end   = end;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            private int current = start;

            @Override public boolean hasNext() { return current < end; }

            @Override public Integer next() {
                if (!hasNext()) throw new NoSuchElementException();
                return current++;
            }
            // remove() not supported — default throws UnsupportedOperationException
        };
    }

    public static void main(String[] args) {
        Range range = new Range(1, 6);

        // Enhanced for-each — works because Range is Iterable
        for (int n : range) System.out.print(n + " "); // 1 2 3 4 5
        System.out.println();

        // Explicit iterator usage
        Iterator<Integer> it = range.iterator();
        while (it.hasNext()) System.out.print(it.next() + " ");
        System.out.println();

        // Stream from Iterable (via StreamSupport)
        import java.util.stream.StreamSupport;
        StreamSupport.stream(range.spliterator(), false)
            .filter(n -> n % 2 == 0)
            .forEach(System.out::print); // 2 4
    }
}
9

Collections Utility Methods

  • Collections.sort requires Comparable elements or a Comparator; uses stable TimSort
  • Collections.binarySearch requires a pre-sorted list — undefined behaviour on unsorted lists
  • Collections.unmodifiableList is a view — the underlying list can still be mutated through its original reference
  • List.of() / Set.of() / Map.of() (Java 9+) create truly immutable collections with no backdoor
  • Collections.synchronizedList requires manual lock on the collection during iteration
  • Collections.disjoint is an efficient O(n) check for shared elements between two collections
CollectionsAlgorithms.java
import java.util.*;

public class CollectionsAlgorithms {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9, 2, 6));

        // Sort + binary search
        Collections.sort(nums);
        System.out.println(nums); // [1, 1, 2, 3, 4, 5, 6, 9]
        int idx = Collections.binarySearch(nums, 5);
        System.out.println("Index of 5: " + idx); // 5

        // Reverse
        Collections.reverse(nums);
        System.out.println(nums); // [9, 6, 5, 4, 3, 2, 1, 1]

        // Shuffle (random order)
        Collections.shuffle(nums, new Random(42)); // seeded for reproducibility
        System.out.println(nums);

        // Rotate — moves last 'distance' elements to front
        List<String> letters = new ArrayList<>(List.of("a","b","c","d","e"));
        Collections.rotate(letters, 2);
        System.out.println(letters); // [d, e, a, b, c]

        // Swap
        Collections.swap(letters, 0, 4);
        System.out.println(letters); // [c, e, a, b, d]

        // Fill and nCopies
        Collections.fill(letters, "x");
        System.out.println(letters); // [x, x, x, x, x]

        List<String> copies = Collections.nCopies(4, "Java");
        System.out.println(copies); // [Java, Java, Java, Java]
    }
}
10

Enhanced for-each

  • Enhanced for-each compiles to an iterator loop for Iterable; index loop for arrays
  • Cannot remove elements inside for-each — use removeIf() or an explicit Iterator
  • Cannot access the index, iterate in reverse, or skip elements with for-each
  • Iterable.forEach(lambda) supports method references but cannot use break/continue
  • For Map, iterate entrySet() with for-each to get both key and value simultaneously
  • Two collections in sync require a classic index-based for loop
ForEachDemo.java
import java.util.*;

public class ForEachDemo {
    public static void main(String[] args) {
        // Array — compiles to index loop
        int[] nums = {1, 2, 3, 4, 5};
        int sum = 0;
        for (int n : nums) sum += n;
        System.out.println(sum); // 15

        // Collection — compiles to iterator
        List<String> names = List.of("Alice", "Bob", "Carol");
        for (String name : names) System.out.print(name + " ");
        System.out.println();

        // Map — iterate entrySet
        Map<String, Integer> scores = Map.of("Alice", 90, "Bob", 85);
        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // 2D array
        int[][] matrix = {{1,2},{3,4},{5,6}};
        for (int[] row : matrix) {
            for (int val : row) System.out.print(val + " ");
        }
        System.out.println();

        // Custom Iterable — see Iterator topic
        // for (int n : new Range(1, 5)) System.out.print(n + " ");
    }
}
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/java