Skip to content
intermediatePhase 15 · Java 8+ Features

Stream API

Process collections with map, filter, reduce, sorted, and collect operations.

1h
3 problems
Topic Progress0%

Creating Streams

Creating Streams

Streams can be created from collections, arrays, values, or generators.

import java.util.*;
import java.util.stream.*;

public class CreatingStreamsDemo {
    public static void main(String[] args) {
        // From Collection
        List<String> list = Arrays.asList("a", "b", "c", "d");
        Stream<String> listStream = list.stream();
        Stream<String> parallelStream = list.parallelStream();

        // From Array
        int[] nums = {1, 2, 3, 4, 5};
        IntStream arrayStream = Arrays.stream(nums);
        Stream<Integer> boxed = Arrays.stream(nums).boxed();

        // From Values
        Stream<String> valueStream = Stream.of("x", "y", "z");
        Stream<Integer> intStream = Stream.of(1, 2, 3, 4, 5);

        // From Generator (infinite)
        Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
        Stream<Integer> ones = Stream.iterate(1, n -> n).limit(5);

        // From String chars
        IntStream chars = "hello".chars();
       .chars().forEach(c -> System.out.print((char) c + " "));
        System.out.println();

        // Range
        IntStream range = IntStream.range(1, 6); // 1,2,3,4,5
        IntStream rangeClosed = IntStream.rangeClosed(1, 5); // 1,2,3,4,5

        // Print streams
        System.out.println("List stream: " + listStream.collect(Collectors.toList()));
        System.out.println("Array stream: " + arrayStream.collect(Collectors.toList()));
        System.out.println("Values: " + valueStream.collect(Collectors.toList()));
        System.out.println("Random: " + randomStream.collect(Collectors.toList()));
        System.out.println("Range: " + range.collect(Collectors.toList()));
    }
}

Stream creation summary:

Source Method
Collection collection.stream() or collection.parallelStream()
Array Arrays.stream(array)
Values Stream.of(values...)
Infinite Stream.generate(supplier)
Sequential Stream.iterate(seed, unaryOp)
Range IntStream.range(start, end)
String string.chars()

Intermediate Operations

Intermediate Operations

Intermediate operations are lazy — they are not executed until a terminal operation is invoked. They return a new Stream.

import java.util.*;
import java.util.stream.*;

public class IntermediateOpsDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList(
            "Alice", "Bob", "Charlie", "David", "Eve",
            "Alice", "Frank", "Grace"
        );

        // filter - select elements matching predicate
        List<String> longNames = names.stream()
            .filter(name -> name.length() > 4)
            .collect(Collectors.toList());
        System.out.println("Long names: " + longNames); // [Alice, Charlie, David, Frank, Grace]

        // map - transform each element
        List<Integer> lengths = names.stream()
            .map(String::length)
            .collect(Collectors.toList());
        System.out.println("Lengths: " + lengths);

        // sorted - natural or custom order
        List<String> sorted = names.stream()
            .sorted()
            .collect(Collectors.toList());
        System.out.println("Sorted: " + sorted);

        List<String> sortedByLength = names.stream()
            .sorted(Comparator.comparingInt(String::length))
            .collect(Collectors.toList());
        System.out.println("By length: " + sortedByLength);

        // distinct - remove duplicates
        List<String> unique = names.stream()
            .distinct()
            .collect(Collectors.toList());
        System.out.println("Unique: " + unique);

        // flatMap - flatten nested structures
        List<List<Integer>> nested = Arrays.asList(
            Arrays.asList(1, 2, 3),
            Arrays.asList(4, 5),
            Arrays.asList(6, 7, 8, 9)
        );
        List<Integer> flat = nested.stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toList());
        System.out.println("Flat: " + flat); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

        // peek - debug/inspect without modifying
        List<String> peeked = names.stream()
            .filter(name -> name.length() > 3)
            .peek(name -> System.out.println("Filtered: " + name))
            .map(String::toUpperCase)
            .peek(name -> System.out.println("Mapped: " + name))
            .collect(Collectors.toList());

        // limit and skip
        List<String> first3 = names.stream()
            .limit(3)
            .collect(Collectors.toList());
        System.out.println("First 3: " + first3); // [Alice, Bob, Charlie]

        List<String> skip3 = names.stream()
            .skip(3)
            .collect(Collectors.toList());
        System.out.println("Skip 3: " + skip3); // [David, Eve, Alice, Frank, Grace]

        // takeWhile and dropWhile (Java 9+)
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 1, 2);
        List<Integer> taken = numbers.stream()
            .takeWhile(n -> n < 4)
            .collect(Collectors.toList());
        System.out.println("Take while < 4: " + taken); // [1, 2, 3]
    }
}

Key intermediate operations:

Operation Description
filter(Predicate) Select elements matching predicate
map(Function) Transform each element
flatMap(Function) Flatten nested streams
sorted() / sorted(Comparator) Sort elements
distinct() Remove duplicates
limit(n) Take first n elements
skip(n) Skip first n elements
peek(Consumer) Inspect without modifying
takeWhile(Predicate) Take while condition true (Java 9+)
dropWhile(Predicate) Drop while condition true (Java 9+)

Terminal Operations

Terminal Operations

Terminal operations trigger the processing of the stream pipeline and produce a result or side effect.

import java.util.*;
import java.util.stream.*;

public class TerminalOpsDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // forEach - perform action on each element
        System.out.print("forEach: ");
        numbers.stream().filter(n -> n % 2 == 0).forEach(n -> System.out.print(n + " "));
        System.out.println(); // 2 4 6 8 10

        // collect - accumulate into collection
        List<Integer> evens = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());
        System.out.println("Evens: " + evens);

        // reduce - combine elements
        int sum = numbers.stream().reduce(0, Integer::sum);
        System.out.println("Sum: " + sum); // 55

        Optional<Integer> max = numbers.stream().reduce(Integer::max);
        System.out.println("Max: " + max.orElse(0)); // 10

        // count - count elements
        long evenCount = numbers.stream().filter(n -> n % 2 == 0).count();
        System.out.println("Even count: " + evenCount); // 5

        // anyMatch - does any element match?
        boolean hasOver10 = numbers.stream().anyMatch(n -> n > 10);
        System.out.println("Any > 10: " + hasOver10); // false

        // allMatch - do all elements match?
        boolean allPositive = numbers.stream().allMatch(n -> n > 0);
        System.out.println("All positive: " + allPositive); // true

        // noneMatch - do no elements match?
        boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
        System.out.println("None negative: " + noneNegative); // true

        // findFirst - find first element
        Optional<Integer> first = numbers.stream().filter(n -> n > 5).findFirst();
        System.out.println("First > 5: " + first.orElse(0)); // 6

        // findAny - find any element (useful in parallel streams)
        Optional<Integer> any = numbers.parallelStream().filter(n -> n > 5).findAny();
        System.out.println("Any > 5: " + any.orElse(0));

        // min and max
        Optional<Integer> minVal = numbers.stream().min(Integer::compareTo);
        Optional<Integer> maxVal = numbers.stream().max(Integer::compareTo);
        System.out.println("Min: " + minVal.orElse(0)); // 1
        System.out.println("Max: " + maxVal.orElse(0)); // 10

        // toArray
        Integer[] array = numbers.stream().toArray(Integer[]::new);
        System.out.println("Array: " + Arrays.toString(array));

        // forEachOrdered - maintains encounter order
        System.out.print("forEachOrdered: ");
        numbers.parallelStream().forEachOrdered(n -> System.out.print(n + " "));
        System.out.println();
    }
}

Terminal operations:

Operation Returns Description
forEach void Perform action on each element
collect R Accumulate into collection
reduce T or Optional Combine elements
count long Count elements
anyMatch boolean Any element matches?
allMatch boolean All elements match?
noneMatch boolean No elements match?
findFirst Optional First element
findAny Optional Any element
min / max Optional Min/max element
toArray T[] Convert to array

Collectors

Collectors

Collectors are pre-built reduction operations for the collect() terminal operation.

import java.util.*;
import java.util.stream.*;

public class CollectorsDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList(
            "Alice", "Bob", "Charlie", "David", "Eve", "Frank"
        );

        // toList / toSet / toCollection
        List<String> list = names.stream().collect(Collectors.toList());
        Set<String> set = names.stream().collect(Collectors.toSet());
        TreeSet<String> treeSet = names.stream()
            .collect(Collectors.toCollection(TreeSet::new));

        // joining
        String joined = names.stream().collect(Collectors.joining(", "));
        System.out.println("Joined: " + joined); // Alice, Bob, Charlie, David, Eve, Frank

        String joinedWithPrefix = names.stream()
            .collect(Collectors.joining(", ", "[", "]"));
        System.out.println("Bracketed: " + joinedWithPrefix); // [Alice, Bob, Charlie, David, Eve, Frank]

        // counting
        long count = names.stream().collect(Collectors.counting());
        System.out.println("Count: " + count); // 6

        // groupingBy
        Map<Integer, List<String>> byLength = names.stream()
            .collect(Collectors.groupingBy(String::length));
        System.out.println("By length: " + byLength);
        // {3=[Bob, Eve], 5=[Alice, David, Frank], 7=[Charlie]}

        Map<Character, List<String>> byFirst = names.stream()
            .collect(Collectors.groupingBy(name -> name.charAt(0)));
        System.out.println("By first char: " + byFirst);

        // groupingBy with downstream collector
        Map<Integer, Long> countByLength = names.stream()
            .collect(Collectors.groupingBy(String::length, Collectors.counting()));
        System.out.println("Count by length: " + countByLength);
        // {3=2, 5=3, 7=1}

        Map<Integer, String> joinedByLength = names.stream()
            .collect(Collectors.groupingBy(
                String::length,
                Collectors.joining(", ")
            ));
        System.out.println("Joined by length: " + joinedByLength);

        // partitioningBy
        Map<Boolean, List<String>> partitioned = names.stream()
            .collect(Collectors.partitioningBy(name -> name.length() > 4));
        System.out.println("Partitioned: " + partitioned);
        // {false=[Bob, Eve], true=[Alice, Charlie, David, Frank]}

        // summarizingInt / summarizingDouble
        IntSummaryStatistics stats = names.stream()
            .collect(Collectors.summarizingInt(String::length));
        System.out.println("Stats: " + stats);
        // count=6, sum=34, min=3, average=5.67, max=7

        // reducing
        String reduced = names.stream()
            .collect(Collectors.reducing("", (a, b) -> a + b + " "));
        System.out.println("Reduced: " + reduced.trim());

        // toMap
        Map<String, Integer> nameLengths = names.stream()
            .collect(Collectors.toMap(
                name -> name,
                String::length
            ));
        System.out.println("Name lengths: " + nameLengths);
    }
}

Key Collectors:

Collector Description
toList() Collect to List
toSet() Collect to Set
joining(delimiter) Join strings
counting() Count elements
groupingBy(classifier) Group by classifier
partitioningBy(predicate) Partition into true/false groups
summarizingInt(extractor) Compute count, sum, min, max, avg
toMap(keyMapper, valueMapper) Collect to Map

Common Patterns

Common Stream Patterns

Frequently used stream patterns for common tasks.

import java.util.*;
import java.util.stream.*;

public class CommonPatternsDemo {
    public static void main(String[] args) {
        // Pattern 1: Filter and collect
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
        List<String> longNames = names.stream()
            .filter(n -> n.length() > 4)
            .collect(Collectors.toList());

        // Pattern 2: Transform and collect
        List<String> upper = names.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());

        // Pattern 3: Sum/Aggregate
        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
        int sum = nums.stream().mapToInt(Integer::intValue).sum();
        double avg = nums.stream().mapToInt(Integer::intValue).average().orElse(0);

        // Pattern 4: Find first match
        Optional<String> first = names.stream()
            .filter(n -> n.startsWith("C"))
            .findFirst();
        first.ifPresent(n -> System.out.println("Found: " + n));

        // Pattern 5: Check if any/all match
        boolean hasDavid = names.stream().anyMatch(n -> n.equals("David"));
        boolean allStartWithA = names.stream().allMatch(n -> n.startsWith("A"));

        // Pattern 6: Grouping
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "Engineering", 95000),
            new Employee("Bob", "Marketing", 70000),
            new Employee("Charlie", "Engineering", 110000),
            new Employee("Diana", "Marketing", 80000)
        );

        Map<String, List<Employee>> byDept = employees.stream()
            .collect(Collectors.groupingBy(e -> e.department));

        Map<String, Double> avgSalaryByDept = employees.stream()
            .collect(Collectors.groupingBy(
                e -> e.department,
                Collectors.averagingDouble(e -> e.salary)
            ));

        // Pattern 7: FlatMap - flatten nested
        List<List<Integer>> nested = Arrays.asList(
            Arrays.asList(1, 2), Arrays.asList(3, 4, 5), Arrays.asList(6)
        );
        List<Integer> flat = nested.stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toList());

        // Pattern 8: Chained operations
        String result = employees.stream()
            .filter(e -> e.salary > 80000)
            .sorted(Comparator.comparingDouble((Employee e) -> e.salary).reversed())
            .map(e -> e.name)
            .collect(Collectors.joining(", "));
        System.out.println("High earners: " + result); // Charlie, Alice

        // Pattern 9: Collect to Map
        Map<String, Double> salaryMap = employees.stream()
            .collect(Collectors.toMap(e -> e.name, e -> e.salary));

        // Pattern 10: Parallel stream for large datasets
        long count = IntStream.range(0, 1000000)
            .parallel()
            .filter(n -> n % 2 == 0)
            .count();
        System.out.println("Even numbers: " + count); // 500000
    }

    static class Employee {
        String name;
        String department;
        double salary;
        Employee(String name, String dept, double salary) {
            this.name = name; this.department = dept; this.salary = salary;
        }
    }
}

Key patterns:

  • Filter + Collect: stream().filter(pred).collect(toList())
  • Map + Collect: stream().map(func).collect(toList())
  • Group: stream().collect(groupingBy(classifier))
  • Join: stream().collect(joining(", "))
  • Find: stream().filter(pred).findFirst()
  • Count: stream().filter(pred).count()

Practice Problems

0/3solved
Word Frequency with Streams

Given a paragraph of text, use streams to find the 3 most frequent words (case-insensitive). Return a map of word to count.

Solution
import java.util.*;
import java.util.stream.*;

public class WordFrequency {
    public static Map<String, Long> topWords(String text, int n) {
        return Arrays.stream(text.toLowerCase().split("\\\W+"))
            .filter(w -> !w.isEmpty())
            .collect(Collectors.groupingBy(w -> w, Collectors.counting()))
            .entrySet().stream()
            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
            .limit(n)
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (a, b) -> a,
                LinkedHashMap::new
            ));
    }
}
Stream Pipeline Processing

Given a list of integers, use streams to: remove duplicates, filter only even numbers, square them, sort them, and return as a list.

Solution
import java.util.*;
import java.util.stream.*;

public class StreamPipeline {
    public static List<Integer> process(List<Integer> numbers) {
        return numbers.stream()
            .distinct()
            .filter(n -> n % 2 == 0)
            .map(n -> n * n)
            .sorted()
            .collect(Collectors.toList());
    }
}
Group and Summarize

Given a list of Product objects (name, category, price), use streams to group by category and calculate the total price per category.

Solution
import java.util.*;
import java.util.stream.*;

public class ProductAnalytics {
    static class Product {
        String name;
        String category;
        double price;
        Product(String name, String category, double price) {
            this.name = name; this.category = category; this.price = price;
        }
    }

    public static Map<String, Double> totalPriceByCategory(List<Product> products) {
        return products.stream()
            .collect(Collectors.groupingBy(
                p -> p.category,
                Collectors.summingDouble(p -> p.price)
            ));
    }
}

Quiz

1. What is the difference between intermediate and terminal operations?

Question 1 options

2. What does Collectors.groupingBy() return?

Question 2 options

3. What does flatMap do that map does not?

Question 3 options

4. Which collector joins strings with a delimiter?

Question 4 options

Flashcards

Question

What are the 3 types of stream operations?

Answer

1) Creating (stream(), of(), generate()) 2) Intermediate (filter, map, sorted, distinct, flatMap) 3) Terminal (collect, forEach, reduce, count, anyMatch). Intermediate are lazy, terminal trigger processing.

Question

How do you convert a Stream to a List?

Answer

Use collect(Collectors.toList()). This is a terminal operation that accumulates stream elements into a List. For unmodifiable list: Collectors.toUnmodifiableList().

Question

What is the difference between map and flatMap?

Answer

map transforms each element to one value (1-to-1). flatMap transforms each element to a stream and flattens all streams (1-to-many). flatMap is used for nested structures.

Question

When should you use parallel streams?

Answer

For large datasets with CPU-intensive operations where ordering doesn't matter. Avoid for small datasets (overhead) or I/O-bound operations. Use parallelStream() or .parallel().

Question

What is Java Streams?

Answer

Java Streams is a key concept in Java programming.

Revision Notes

Key Takeaways

  • 1.Streams are lazy — intermediate ops don't execute until a terminal op
  • 2.filter selects, map transforms, flatMap flattens
  • 3.Collectors.groupingBy groups, joining concatenates, counting counts
  • 4.Use parallel streams for large datasets with CPU-intensive work

Interview Tips

  • Explain the difference between intermediate and terminal operations
  • Demonstrate common stream pipelines: filter+map+collect
  • Discuss when to use parallel streams vs sequential
  • Know the key Collectors: toList, groupingBy, joining, counting

Cheat Sheet

Java Streams Cheat Sheet

Creating

  • collection.stream()
  • Arrays.stream(array)
  • Stream.of(values...)
  • Stream.generate(supplier)
  • Stream.iterate(seed, func)
  • IntStream.range(start, end)

Intermediate (Lazy)

  • filter(Predicate)
  • map(Function)
  • flatMap(Function)
  • sorted() / sorted(Comparator)
  • distinct()
  • limit(n) / skip(n)
  • peek(Consumer)

Terminal

  • collect(Collector)
  • forEach(Consumer)
  • reduce(identity, accumulator)
  • count()
  • anyMatch / allMatch / noneMatch
  • findFirst / findAny
  • min / max

Collectors

  • toList() / toSet()
  • joining(delimiter)
  • groupingBy(classifier)
  • partitioningBy(predicate)
  • summingDouble(extractor)
  • counting()