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