Java Stream API — Cheat Sheet

java.util.stream · Collectors · Comparator
GroupingBy + collectingAndThen
Oldest (min joining year) employee per department
Map<String, Employee> ans1 = empList.stream()
  .collect(Collectors.groupingBy(
    Employee::getDeptName,
    Collectors.collectingAndThen(
      Collectors.minBy(
        Comparator.comparing(Employee::getYearOfJoining)),
      Optional::get)));
collectingAndThen wraps any collector + a finisher function
Multi-key Sorting
Age ASC, then Name DESC
// ✦ equivalent forms:
List<Employee> ans2 = empList.stream()
  .sorted(Comparator.comparing(Employee::getAge)
    .reversed()
    .thenComparing(Employee::getName)
    .reversed())  // reverses ENTIRE chain
  .toList();

List<Employee> ans3 = empList.stream()
  .sorted(Comparator.comparing(Employee::getAge)
    .thenComparing(Employee::getName,
      Comparator.reverseOrder())) // only name DESC
  .toList();
SummaryStatistics
DoubleSummaryStatistics stats = empList.stream()
  .collect(Collectors.summarizingDouble(Employee::getSalary));
.getCount().getSum().getMin().getMax().getAverage()
Also: IntSummaryStatistics · LongSummaryStatistics
Top-N per Group
Top 2 highest-paid employees in each department
Map<String, List<Employee>> ans5 = empList.stream()
  .collect(Collectors.groupingBy(
    Employee::getDeptName,
    Collectors.collectingAndThen(
      Collectors.toList(),
      list -> list.stream()
        .sorted(Comparator.comparing(
          Employee::getSalary, Comparator.reverseOrder()))
        .limit(2).toList())));
Nested GroupingBy (2-level)
Lowest-paid per dept AND gender
Map<String, Map<String, Optional<Employee>>> ans6 =
  empList.stream().collect(
    Collectors.groupingBy(Employee::getDeptName,
      Collectors.groupingBy(Employee::getGender,
        Collectors.minBy(
          Comparator.comparing(Employee::getSalary)))));
groupingBy overloads: (classifier) · (classifier, downstream) · (classifier, mapFactory, downstream)
Max-by-Key Trick (salaryMap)
Highest-paid employee(s) per dept via nested grouping on salary
Map<String, List<Employee>> ans7 = empList.stream()
  .collect(Collectors.groupingBy(
    Employee::getDeptName,
    Collectors.collectingAndThen(
      Collectors.groupingBy(Employee::getSalary),
      salaryMap -> salaryMap.entrySet().stream()
        .max(Map.Entry.comparingByKey())
        .map(Map.Entry::getValue).get())));
toMap · partitioningBy · joining · averaging · summing
toMap with merge function
Collectors.toMap(
  Employee::getId,         // key
  e -> e,                 // value
  (existing, newer) -> existing
);
partitioningBy → Boolean keys
Collectors.partitioningBy(
  e -> e.getSalary() > 50000
);
// → Map<Boolean, List<Employee>>
Aggregation collectors
.collect(Collectors.joining(", "))
Collectors.averagingInt(Employee::getAge)
Collectors.summingDouble(Employee::getSalary)
flatMap · IntStream · chars · iterate
// flatMap: each element → sub-stream, all merged
list.stream()
  .flatMap(e -> e.getProjects().stream());

// int[] → IntStream → object stream
Arrays.stream(arr)
  .mapToObj(i -> "#" + i);

// String chars as characters
str.chars()
  .mapToObj(c -> (char) c);

// range: 1 inclusive, 100 exclusive
IntStream.range(1, 100);
IntStream.rangeClosed(1, 100);
Stream.iterate — infinite Fibonacci via seed array
// Stream.iterate: seed + unary operator → infinite stream
Stream.iterate(
    new long[]{ 0, 1 },
    f -> new long[]{ f[1], f[0] + f[1] })
  .limit(10)
  .map(f -> f[0])
  .forEach(System.out::println);
reduce · Parallel Streams
// accumulator only → Optional (like max, min)
stream.reduce((a, b) -> a + b);

// identity + accumulator → T (never empty)
stream.reduce(0, Integer::sum);

// parallel: identity + accumulator + combiner
parallelStream.reduce(0,
  (a, b) -> a + b,
  (a, b) -> a + b);  // combiner merges sub-results
// hint JVM: order doesn't matter → better distribution
list.parallelStream().unordered()
  .filter(e -> e.getAge() > 30);
Use unordered() in parallel streams when result order is irrelevant
Stream Tricks & Common Patterns
Prime number filter
// Filter prime numbers
List<Integer> primes = nums.stream()
  .filter(n -> n > 1 && IntStream.range(2, (int) Math.sqrt(n) + 1)
    .noneMatch(i -> n % i == 0))
  .toList();
Factorial via reduce
// Factorial using reduce
int fact = IntStream.rangeClosed(1, n)
  .reduce(1, (a, b) -> a * b);
Reverse a string
// Reverse a string with streams
String reversed = Arrays.stream(str.split(""))
  .reduce("", (a, b) -> b + a);
Reverse each word
// Reverse each word in a sentence
String result = Arrays.stream(str.split(" "))
  .map(s -> new StringBuilder(s).reverse().toString())
  .collect(Collectors.joining(" "));
Sum digits (char to int)
// Sum digits of a numeric string
int sum = str.chars()
  .map(c -> c - '0')  // char to int digit
  .sum();
First duplicate number
// First duplicate number
Optional<Integer> dup = list.stream()
  .collect(Collectors.groupingBy(
    Function.identity(), LinkedHashMap::new,
    Collectors.counting()))
  .entrySet().stream()
  .filter(e -> e.getValue() > 1)
  .map(Map.Entry::getKey)
  .findFirst();
Flatten comma-separated strings
// Flatten comma-separated strings
List<String> flat = list.stream()
  .flatMap(s -> Arrays.stream(s.split(",")))
  .toList();
List intersection & union
// Intersection of two lists
List<Integer> inter = list1.stream()
  .filter(list2::contains).toList();

// Union of two lists
List<Integer> union = Stream.concat(
  list1.stream(), list2.stream()).distinct().toList();
Sort by frequency
// Sort elements by frequency
Map<Integer, Long> freqMap = list.stream()
  .collect(Collectors.groupingBy(
    Function.identity(), Collectors.counting()));
List<Integer> sorted = freqMap.entrySet().stream()
  .sorted(Map.Entry.<Integer,Long>comparingByValue().reversed()
    .thenComparing(Map.Entry::getKey))
  .flatMap(e -> Collections.nCopies(
    e.getValue().intValue(), e.getKey()).stream())
  .toList();
Group anagrams
// Group anagrams together
Map<String, List<String>> anagrams = words.stream()
  .collect(Collectors.groupingBy(s ->
    s.chars().sorted()
      .collect(StringBuilder::new,
        StringBuilder::appendCodePoint,
        StringBuilder::append).toString()));
Custom Collectors · Parallel Stream Tips · Sorting · groupingBy extras
collect(supplier, accumulator, combiner) & Collector.of
// collect with supplier, accumulator, combiner
List<String> result = stream.collect(
  ArrayList::new,     // supplier
  ArrayList::add,     // accumulator
  ArrayList::addAll  // combiner (parallel)
);

// Custom Collector.of
Collector.of(
  ArrayList::new,
  List::add,
  (l1, l2) -> { l1.addAll(l2); return l1; }
);
Sorting tips

thenComparing added after comparing — can pass Comparator.reverseOrder() as second param
• Sort alphabetically: just use s → s inside comparing
• No isUpperCase() in streams — use s → s.equals(s.toUpperCase())
• When streaming entrySet: e → e.getValue() and Map.Entry.comparingByKey() / comparingByValue()

groupingBy downstream collectors

Collectors.counting() — count per group
Collectors.minBy(cmp) / maxBy(cmp)
Collectors.summingInt() / averagingDouble()
stream().mapToInt(Integer::intValue).summaryStatistics()

Parallel stream rules

• Add combiner as 3rd param to reduce: e.g. Integer::sum
• Only use associative operations: a+b=b+a ✓
• Non-associative ops (subtraction) give wrong results with parallel ✗
Files.lines(path) — process large files lazily

min, max, findFirst, findAny all return Optional

Always use .orElse() or .orElseThrow() to unwrap

Terminal ops returning Optional · Short-circuit ops · Stream sources

Return Optional

findFirst()findAny()min(cmp)max(cmp)reduce(acc)

Short-circuit

anyMatch()allMatch()noneMatch()findFirst()limit(n)

Common sources

Collection.stream()Arrays.stream(arr)Stream.of(...)IntStream.range()Files.lines(path)