CoreJava Java8

Java 8 – Stream API: filter, map, reduce, collect and More

Java 8 – Stream API: filter, map, reduce, collect and More

The Stream API (java.util.stream) is the most powerful practical addition of Java 8. It lets you process sequences of data — lists, arrays, sets — using a declarative pipeline: describe what you want (filter, transform, aggregate), and the Stream handles the how. This tutorial covers every important stream operation with real examples.

What is a Stream?

A Stream is a sequence of elements from a source (a collection, array, or generator) that supports sequential or parallel aggregate operations. Key properties:

  • Not a data structure — streams don't store data; they process it
  • Lazy — intermediate operations are only executed when a terminal operation is called
  • Single use — once consumed, a stream cannot be reused
  • Can be parallel — swap .stream() for .parallelStream() to use multiple CPU cores

Stream Pipeline Structure

source → [intermediate operations] → terminal operation

Creating Streams

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

// From a List
Stream<String> s1 = List.of("a", "b", "c").stream();

// From an array
Stream<String> s2 = Arrays.stream(new String[]{"x", "y", "z"});

// From values directly
Stream<Integer> s3 = Stream.of(1, 2, 3, 4, 5);

// Primitive streams — no boxing overhead
IntStream    ints    = IntStream.range(1, 6);       // 1,2,3,4,5
LongStream   longs   = LongStream.rangeClosed(1, 5); // 1,2,3,4,5
DoubleStream doubles = DoubleStream.of(1.1, 2.2, 3.3);

// Infinite stream — must use limit()
Stream<Integer> infinite = Stream.iterate(0, n -> n + 2); // 0,2,4,6,...
Stream<Double>  randoms  = Stream.generate(Math::random);

Intermediate Operations

filter() – Keep matching elements

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> evens = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
System.out.println(evens);  // [2, 4, 6, 8, 10]

map() – Transform each element

List<String> names = Arrays.asList("alice", "bob", "charlie");

List<String> upper = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.println(upper);  // [ALICE, BOB, CHARLIE]

// Map to different type
List<Integer> lengths = names.stream()
    .map(String::length)
    .collect(Collectors.toList());
System.out.println(lengths);  // [5, 3, 7]

flatMap() – Flatten nested collections

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);  // [1, 2, 3, 4, 5, 6, 7, 8, 9]

sorted() – Sort elements

List<String> words = Arrays.asList("banana", "apple", "cherry", "date");

// Natural order
words.stream().sorted().forEach(System.out::println);
// apple, banana, cherry, date

// Custom comparator — by length, then alphabetically
words.stream()
     .sorted(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()))
     .forEach(System.out::println);
// date, apple, banana, cherry

distinct() – Remove duplicates

List<Integer> nums = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
nums.stream().distinct().forEach(System.out::print);  // 1234

limit() and skip()

// limit — take first N elements
Stream.iterate(1, n -> n + 1)
      .limit(5)
      .forEach(System.out::print);  // 12345

// skip — skip first N elements
List<String> names = Arrays.asList("A", "B", "C", "D", "E");
names.stream().skip(2).forEach(System.out::print);  // CDE

peek() – Debug without changing the stream

List<Integer> result = Arrays.asList(1, 2, 3, 4, 5)
    .stream()
    .filter(n -> n % 2 == 0)
    .peek(n -> System.out.println("After filter: " + n))
    .map(n -> n * n)
    .peek(n -> System.out.println("After map: " + n))
    .collect(Collectors.toList());
// After filter: 2 → After map: 4 → After filter: 4 → After map: 16 ...

Terminal Operations

collect() – Gather results into a collection

import java.util.stream.Collectors;

List<String> names = Arrays.asList("Alice", "Bob", "Alice", "Charlie", "Bob", "Alice");

// To List
List<String> list = names.stream().collect(Collectors.toList());

// To Set (removes duplicates)
Set<String> set = names.stream().collect(Collectors.toSet());

// Join to String
String joined = names.stream().collect(Collectors.joining(", "));
System.out.println(joined);   // Alice, Bob, Alice, Charlie, Bob, Alice

// Count by value
Map<String, Long> frequency = names.stream()
    .collect(Collectors.groupingBy(n -> n, Collectors.counting()));
System.out.println(frequency);  // {Alice=3, Bob=2, Charlie=1}

// Group by first letter
Map<Character, List<String>> grouped = names.stream()
    .distinct()
    .collect(Collectors.groupingBy(n -> n.charAt(0)));
System.out.println(grouped);  // {A=[Alice], B=[Bob], C=[Charlie]}

reduce() – Aggregate to a single value

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Sum using reduce
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println("Sum: " + sum);       // Sum: 15

// Product
int product = numbers.stream().reduce(1, (a, b) -> a * b);
System.out.println("Product: " + product);  // Product: 120

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

count(), sum(), min(), max(), average()

List<Integer> nums = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);

System.out.println("Count:   " + nums.stream().count());                        // 8
System.out.println("Sum:     " + nums.stream().mapToInt(i -> i).sum());         // 31
System.out.println("Min:     " + nums.stream().mapToInt(i -> i).min().getAsInt()); // 1
System.out.println("Max:     " + nums.stream().mapToInt(i -> i).max().getAsInt()); // 9
System.out.println("Average: " + nums.stream().mapToInt(i -> i).average().getAsDouble()); // 3.875

findFirst(), findAny(), anyMatch(), allMatch(), noneMatch()

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

Optional<String> first = names.stream()
    .filter(n -> n.startsWith("C"))
    .findFirst();
System.out.println(first.orElse("not found"));  // Charlie

boolean anyMatch  = names.stream().anyMatch(n -> n.length() > 5);  // true (Charlie)
boolean allMatch  = names.stream().allMatch(n -> n.length() > 2);  // true
boolean noneMatch = names.stream().noneMatch(n -> n.isEmpty());     // true

Complete Example – Product Catalog Analysis

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

class Product {
    String name; String category; double price; int quantity;
    Product(String n, String c, double p, int q) {
        name=n; category=c; price=p; quantity=q; }
    public String toString() { return name + "(₹" + price + ")"; }
}

public class StreamDemo {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product("Laptop",     "Electronics", 75000, 10),
            new Product("Smartphone", "Electronics", 25000, 50),
            new Product("Tablet",     "Electronics", 35000, 30),
            new Product("Keyboard",   "Accessories",  2500, 100),
            new Product("Mouse",      "Accessories",  1500, 200),
            new Product("Monitor",    "Electronics", 15000, 20)
        );

        // 1. Average price of Electronics
        OptionalDouble avgPrice = products.stream()
            .filter(p -> p.category.equals("Electronics"))
            .mapToDouble(p -> p.price)
            .average();
        System.out.printf("Avg Electronics price: ₹%.0f%n", avgPrice.getAsDouble());
        // ₹37500

        // 2. Most expensive product
        products.stream()
            .max(Comparator.comparingDouble(p -> p.price))
            .ifPresent(p -> System.out.println("Most expensive: " + p));
        // Laptop(₹75000.0)

        // 3. Group products by category
        Map<String, List<Product>> byCategory = products.stream()
            .collect(Collectors.groupingBy(p -> p.category));
        byCategory.forEach((cat, list) ->
            System.out.println(cat + ": " + list));

        // 4. Total inventory value (price × quantity)
        double totalValue = products.stream()
            .mapToDouble(p -> p.price * p.quantity)
            .sum();
        System.out.printf("Total inventory value: ₹%.0f%n", totalValue);
        // ₹4,025,000

        // 5. Names of products under ₹10000, sorted
        String cheap = products.stream()
            .filter(p -> p.price < 10000)
            .sorted(Comparator.comparingDouble(p -> p.price))
            .map(p -> p.name)
            .collect(Collectors.joining(", "));
        System.out.println("Cheap products: " + cheap);
        // Mouse, Keyboard
    }
}

Parallel Streams

// Sequential (default)
long count1 = IntStream.range(1, 1_000_001).filter(n -> n % 2 == 0).count();

// Parallel — uses all CPU cores automatically
long count2 = IntStream.range(1, 1_000_001).parallel().filter(n -> n % 2 == 0).count();

System.out.println(count1);  // 500000
System.out.println(count2);  // 500000 (same result, faster on large datasets)

Summary

The Stream API transforms collection processing from imperative loops into declarative pipelines. The structure is always: source → zero or more intermediate operations → one terminal operation. Key intermediate operations: filter(), map(), flatMap(), sorted(), distinct(). Key terminal operations: collect(), reduce(), forEach(), count(), findFirst(). Use parallelStream() for CPU-bound operations on large datasets. Use Collectors.groupingBy() to replace complex for-loop grouping logic with a single line.

Topics: CoreJava Java8
← Newer Post Older Post →

Comments

https://www.blogger.com/comment/frame/6690124484600543990?po=2041903889395274608&hl=en&saa=85391&origin=https://www.java9r.com