Ch 14.2 Intermediate Operations
Intermediate operations form the stream pipeline and specify which processing logic to apply. The result of an intermediate operation always returns a Stream, so multiple intermediate operations can be chained together.
Intermediate operations are lazy— they are not actually executed until a terminal operation is called.
1. filter() — Conditional Filtering
Takes a Predicate<T> and passes only elements where the condition is true.
import java.util.*;
import java.util.stream.*;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Keep only even numbers
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> System.out.print(n + " ")); // 2 4 6 8 10
System.out.println();
// Combining multiple conditions (&&, ||, !)
List<String> words = Arrays.asList("Java", "Python", "Go", "JavaScript", "Kotlin", "Rust");
words.stream()
.filter(w -> w.length() >= 4) // at least 4 characters
.filter(w -> w.contains("a")) // contains 'a'
.filter(w -> !w.startsWith("J")) // does not start with J
.forEach(System.out::println); // Python, Kotlin
}
}
// filter with object streams
record Product(String name, String category, int price, boolean inStock) {}
public class FilterObjectExample {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product("Laptop", "Electronics", 1_200_000, true),
new Product("Mouse", "Electronics", 35_000, true),
new Product("Keyboard", "Electronics", 80_000, false),
new Product("Desk", "Furniture", 250_000, true),
new Product("Chair", "Furniture", 180_000, true)
);
// In-stock electronics priced at or below 100,000
products.stream()
.filter(Product::inStock)
.filter(p -> p.category().equals("Electronics"))
.filter(p -> p.price() <= 100_000)
.forEach(p -> System.out.println(p.name() + ": " + p.price()));
// Mouse: 35000
}
}
2. map() — Transformation
Takes a Function<T, R> and transforms each element into a different value.
import java.util.*;
import java.util.stream.*;
public class MapExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// Convert strings to uppercase
words.stream()
.map(String::toUpperCase)
.forEach(System.out::println); // APPLE, BANANA, CHERRY
// String to length (type conversion)
words.stream()
.map(String::length)
.forEach(n -> System.out.print(n + " ")); // 5 6 6
System.out.println();
// Extract first character
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.map(name -> name.charAt(0))
.forEach(c -> System.out.print(c + " ")); // A B C
System.out.println();
// Square each number
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.map(n -> n * n)
.forEach(n -> System.out.print(n + " ")); // 1 4 9 16 25
System.out.println();
}
}
mapToInt, mapToDouble, mapToLong
Converts an object stream to a primitive stream, enabling statistical methods like sum() and average().
record Employee(String name, double salary) {}
public class MapToIntExample {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", 5_500_000),
new Employee("Bob", 4_200_000),
new Employee("Charlie", 6_800_000)
);
// Stream<Employee> -> DoubleStream
double totalSalary = employees.stream()
.mapToDouble(Employee::salary)
.sum();
System.out.printf("Total salary: %,.0f%n", totalSalary);
double avgSalary = employees.stream()
.mapToDouble(Employee::salary)
.average()
.orElse(0.0);
System.out.printf("Average salary: %,.0f%n", avgSalary);
}
}
3. flatMap() — Flattening Nested Collections
Flattens nested collection structures into a single stream.
import java.util.*;
import java.util.stream.*;
public class FlatMapExample {
public static void main(String[] args) {
// Flatten a list of lists into a single stream
List<List<Integer>> nestedNumbers = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
nestedNumbers.stream()
.flatMap(Collection::stream) // convert each list to stream and merge
.forEach(n -> System.out.print(n + " ")); // 1 2 3 4 5 6 7 8 9
System.out.println();
// Split strings into individual words
List<String> sentences = Arrays.asList(
"Hello World",
"Java Stream API",
"FlatMap Example"
);
sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.map(String::toLowerCase)
.distinct()
.sorted()
.forEach(word -> System.out.print(word + " "));
System.out.println();
}
}
// map vs flatMap comparison
public class MapVsFlatMap {
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World");
// map: produces Stream<Stream<String>> (undesired result)
Stream<Stream<String>> mapResult = words.stream()
.map(word -> Arrays.stream(word.split("")));
System.out.println("map result type: Stream<Stream<String>>");
// flatMap: produces Stream<String> (desired result - all chars in one stream)
words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.distinct()
.sorted()
.forEach(c -> System.out.print(c + " ")); // H W d e l o r
System.out.println();
}
}
4. distinct() — Removing Duplicates
public class DistinctExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5, 1);
numbers.stream()
.distinct()
.forEach(n -> System.out.print(n + " ")); // 1 2 3 4 5
System.out.println();
// For objects, equals() and hashCode() must be correctly implemented.
// Records automatically implement equals/hashCode.
record Point(int x, int y) {}
List<Point> points = Arrays.asList(
new Point(1, 2), new Point(3, 4), new Point(1, 2), new Point(5, 6)
);
points.stream()
.distinct()
.forEach(p -> System.out.print(p + " ")); // Point[1,2] Point[3,4] Point[5,6]
System.out.println();
}
}
5. sorted() — Sorting
import java.util.*;
import java.util.stream.*;
public class SortedExample {
public static void main(String[] args) {
// Natural ordering (requires Comparable implementation)
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 9, 2, 7);
numbers.stream()
.sorted()
.forEach(n -> System.out.print(n + " ")); // 1 2 3 5 7 8 9
System.out.println();
// Reverse ordering
numbers.stream()
.sorted(Comparator.reverseOrder())
.forEach(n -> System.out.print(n + " ")); // 9 8 7 5 3 2 1
System.out.println();
// Object sorting with Comparator
record Person(String name, int age) {}
List<Person> people = Arrays.asList(
new Person("Charlie", 25),
new Person("Alice", 30),
new Person("Bob", 20),
new Person("David", 25)
);
// Sort by age ascending
people.stream()
.sorted(Comparator.comparingInt(Person::age))
.forEach(p -> System.out.println(p.name() + ": " + p.age()));
System.out.println();
// Sort by age ascending, then by name ascending (compound sort)
people.stream()
.sorted(Comparator.comparingInt(Person::age)
.thenComparing(Person::name))
.forEach(p -> System.out.println(p.name() + ": " + p.age()));
}
}
6. limit() and skip() — Size Limiting and Skipping
public class LimitSkipExample {
public static void main(String[] args) {
// limit: take at most n elements
Stream.iterate(1, n -> n + 1)
.limit(5)
.forEach(n -> System.out.print(n + " ")); // 1 2 3 4 5
System.out.println();
// skip: skip the first n elements
IntStream.rangeClosed(1, 10)
.skip(3)
.forEach(n -> System.out.print(n + " ")); // 4 5 6 7 8 9 10
System.out.println();
// Implementing pagination (3 items per page, page 2)
List<String> items = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I");
int pageSize = 3;
int pageNum = 2; // 0-based
List<String> page = items.stream()
.skip((long) pageNum * pageSize) // skip previous pages
.limit(pageSize) // take current page size
.collect(Collectors.toList());
System.out.println("Page 2: " + page); // [G, H, I]
}
}
7. peek() — Intermediate Debugging
Similar to forEach but returns the stream, so it can be inserted in the pipeline for debugging.
public class PeekExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("original: " + n))
.filter(n -> n % 2 == 0)
.peek(n -> System.out.println(" passed filter: " + n))
.map(n -> n * 3)
.peek(n -> System.out.println(" after map: " + n))
.collect(Collectors.toList());
System.out.println("Final result: " + result);
}
}
Use peek() for debugging purposes only. Placing code with side effects inside peek() can cause unpredictable behavior in parallel streams.
8. Performance Impact of Operation Order
The order of intermediate operations affects performance.
import java.util.*;
import java.util.stream.*;
public class OperationOrderExample {
static int filterCount = 0;
static int mapCount = 0;
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Inefficient order: map first -> 10 map calls, then filter
filterCount = 0; mapCount = 0;
numbers.stream()
.map(n -> { mapCount++; return n * 2; })
.filter(n -> { filterCount++; return n > 10; })
.collect(Collectors.toList());
System.out.println("map first - map calls: " + mapCount + ", filter calls: " + filterCount);
// map: 10, filter: 10
// Efficient order: filter first -> only passing elements go through map
filterCount = 0; mapCount = 0;
numbers.stream()
.filter(n -> { filterCount++; return n > 5; })
.map(n -> { mapCount++; return n * 2; })
.collect(Collectors.toList());
System.out.println("filter first - filter calls: " + filterCount + ", map calls: " + mapCount);
// filter: 10, map: 5 (only half the elements go through map)
}
}
Performance optimization principles:
- Place
filterearly in the pipeline to reduce the number of elements processed. - Defer expensive operations (
map,sort) as late as possible. - Combining
limitwithfindFirst/findAnyuses short-circuit evaluation to avoid processing all elements.
9. Practical Example: Product Pipeline
import java.util.*;
import java.util.stream.*;
record Product(String name, String category, int price, double rating, boolean inStock) {}
public class ProductPipelineExample {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product("Laptop Pro", "Electronics", 1_800_000, 4.8, true),
new Product("Wireless Mouse", "Electronics", 45_000, 4.5, true),
new Product("USB Hub", "Electronics", 28_000, 4.2, false),
new Product("Ergonomic Chair", "Furniture", 450_000, 4.9, true),
new Product("Standing Desk", "Furniture", 680_000, 4.7, true),
new Product("27-inch Monitor", "Electronics", 520_000, 4.6, true),
new Product("Keyboard", "Electronics", 150_000, 4.3, true),
new Product("Webcam", "Electronics", 85_000, 4.1, false)
);
System.out.println("=== In-stock Electronics (100k-2M, Rating 4.4+, by price) ===");
products.stream()
.filter(Product::inStock)
.filter(p -> p.category().equals("Electronics"))
.filter(p -> p.price() >= 100_000 && p.price() <= 2_000_000)
.filter(p -> p.rating() >= 4.4)
.sorted(Comparator.comparingInt(Product::price))
.map(p -> String.format("%-20s | %,8d | %.1f stars",
p.name(), p.price(), p.rating()))
.forEach(System.out::println);
System.out.println("\n=== Product names by category ===");
Map<String, List<String>> byCategory = products.stream()
.collect(Collectors.groupingBy(
Product::category,
Collectors.mapping(Product::name, Collectors.toList())
));
byCategory.forEach((cat, names) ->
System.out.println(cat + ": " + names));
System.out.println("\n=== Top 3 most expensive products ===");
products.stream()
.sorted(Comparator.comparingInt(Product::price).reversed())
.limit(3)
.map(Product::name)
.forEach(System.out::println);
}
}