Fundamentals 10 min read

Master Java Stream API: filter, map, collect in One Line

This article introduces Java 8's Stream API, explaining its lazy, declarative processing model, how to create streams from collections or arrays, and demonstrates core intermediate and terminal operations with concrete code examples and a practical employee‑salary use case.

CodeNotes
CodeNotes
CodeNotes
Master Java Stream API: filter, map, collect in One Line

Introduction

Before Java 8, filtering, transforming, and aggregating collections required verbose loops and conditionals, making code hard to maintain. The Stream API offers a functional, declarative way to handle collections, turning complex data operations into elegant chained calls.

Stream Processing Flow

Data source (Collection/Array)
    ↓
Intermediate operations (lazy, chainable)
    filter → map → sorted → distinct → limit …
    ↓
Terminal operations (trigger execution, can be called only once)
    collect → forEach → count → reduce → findFirst …
Stream is lazy : intermediate operations do not execute immediately; execution is triggered only when a terminal operation is invoked.

Creating Streams

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

// From a collection
List<String> list = List.of("Java", "Python", "Go");
Stream<String> s1 = list.stream();

// From an array
int[] arr = {1, 2, 3, 4, 5};
IntStream s2 = Arrays.stream(arr);

// Direct creation
Stream<String> s3 = Stream.of("a", "b", "c");

// Infinite streams
Stream<Integer> s4 = Stream.iterate(0, n -> n + 2).limit(5); // 0,2,4,6,8
Stream<Double> s5 = Stream.generate(Math::random).limit(3);

Common Intermediate Operations

filter (filtering)

List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = nums.stream()
    .filter(n -> n % 2 == 0) // keep only even numbers
    .collect(Collectors.toList());
System.out.println(evens); // [2, 4, 6, 8, 10]

map (transformation)

List<String> names = List.of("alice", "bob", "charlie");
// Upper‑case strings
List<String> upper = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.println(upper); // [ALICE, BOB, CHARLIE]

// Map strings to their lengths
List<String> langs = List.of("Java", "Python", "Go");
List<Integer> lengths = langs.stream()
    .map(String::length)
    .collect(Collectors.toList());
System.out.println(lengths); // [4, 6, 2]

sorted (sorting)

List<Integer> nums = List.of(5, 3, 8, 1, 9, 2);
// Natural order
List<Integer> sorted = nums.stream()
    .sorted()
    .collect(Collectors.toList());
System.out.println(sorted); // [1, 2, 3, 5, 8, 9]

// Custom order (descending)
List<Integer> desc = nums.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());
System.out.println(desc); // [9, 8, 5, 3, 2, 1]

distinct / limit / skip

List<Integer> nums = List.of(1, 2, 2, 3, 3, 3, 4, 5);
nums.stream().distinct().forEach(n -> System.out.print(n + " ")); // 1 2 3 4 5

List<Integer> first3 = nums.stream().limit(3).collect(Collectors.toList()); // [1, 2, 2]
List<Integer> skip3 = nums.stream().skip(3).collect(Collectors.toList()); // [3, 3, 4, 5]

Terminal Operations

collect (gathering)

import java.util.stream.Collectors;

List<String> langs = List.of("Java", "Python", "Go", "Java", "Rust");
// To List
List<String> list = langs.stream().collect(Collectors.toList());
// To Set (deduplication)
Set<String> set = langs.stream().collect(Collectors.toSet());
// To Map
Map<String, Integer> map = langs.stream()
    .distinct()
    .collect(Collectors.toMap(s -> s, String::length));
System.out.println(map); // {Java=4, Python=6, Go=2, Rust=4}
// Grouping and counting (frequency)
Map<String, Long> freq = langs.stream()
    .collect(Collectors.groupingBy(s -> s, Collectors.counting()));
System.out.println(freq); // {Java=2, Python=1, Go=1, Rust=1}
// Joining strings
String joined = langs.stream()
    .distinct()
    .collect(Collectors.joining(", ", "[", "]"));
System.out.println(joined); // [Java, Python, Go, Rust]

count / findFirst / anyMatch

List<String> langs = List.of("Java", "Python", "Go", "JavaScript");
long count = langs.stream().filter(s -> s.startsWith("J")).count();
System.out.println(count); // 2
Optional<String> first = langs.stream().filter(s -> s.length() > 4).findFirst();
first.ifPresent(System.out::println); // Python
boolean anyMatch = langs.stream().anyMatch(s -> s.equals("Go")); // true
boolean allMatch = langs.stream().allMatch(s -> s.length() > 1); // true
boolean noneMatch = langs.stream().noneMatch(s -> s.isEmpty()); // true

reduce (aggregation)

List<Integer> nums = List.of(1, 2, 3, 4, 5);
// Sum
int sum = nums.stream().reduce(0, Integer::sum);
System.out.println(sum); // 15
// Max value
Optional<Integer> max = nums.stream().reduce(Integer::max);
max.ifPresent(System.out::println); // 5

Comprehensive Practice

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

record Employee(String name, String dept, double salary) {}

public class StreamPractice {
    public static void main(String[] args) {
        List<Employee> employees = List.of(
            new Employee("张三", "研发", 18000),
            new Employee("李四", "研发", 22000),
            new Employee("王五", "市场", 15000),
            new Employee("赵六", "市场", 17000),
            new Employee("钱七", "研发", 25000)
        );

        // Requirement 1: Names of R&D staff with salary > 20000
        List<String> devHighSalary = employees.stream()
            .filter(e -> "研发".equals(e.dept()))
            .filter(e -> e.salary() > 20000)
            .map(Employee::name)
            .collect(Collectors.toList());
        System.out.println("高薪研发:" + devHighSalary); // [李四, 钱七]

        // Requirement 2: Average salary per department
        Map<String, Double> avgSalaryByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::dept,
                Collectors.averagingDouble(Employee::salary)
            ));
        avgSalaryByDept.forEach((dept, avg) ->
            System.out.printf("%s 平均薪资:%.0f%n", dept, avg));

        // Requirement 3: Top 3 salaries descending
        employees.stream()
            .sorted(Comparator.comparingDouble(Employee::salary).reversed())
            .limit(3)
            .forEach(e -> System.out.println(e.name() + ": " + e.salary()));
    }
}

Common Pitfalls

Stream reuse : a Stream can be used only once; after a terminal operation the Stream is closed.

Empty streams : findFirst() returns an Optional; avoid calling get() without checking.

Modifying the source collection : Stream operations do not alter the original collection; collect the results with collect.

Parallel streams : parallelStream() runs in multiple threads; ensure thread‑safety and be aware of ordering.

Learning Summary

Intermediate ops → filter, map, sorted, distinct, limit
Terminal ops → collect, count, forEach, reduce
Collectors → toList, toSet, toMap, groupingBy, joining
Principle → declarative description of "what to do", not "how to do it"
Stream API upgrades collection handling from "imperative" to "declarative", making code shorter and intent clearer!
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Functional Programmingmaplazy evaluationFilterterminal operationsJava Streamcollect
CodeNotes
Written by

CodeNotes

Discuss code and AI, and document daily life and personal growth.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.