Here’s a professional-grade reference document on Java Streams designed to be both beginner-friendly and useful for daily development. It is structured for practical, real-world usage: concise, example-driven, and organized for quick lookup.
Java Streams: Practical Reference (Beginner to Advanced)
✅ Introduction
Java Streams (Java 8+) allow functional-style operations on collections, enabling concise, readable, and parallelizable data processing.
List<String> names = List.of("Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);🔰 Core Concepts
| Term | Description |
|---|---|
| Stream | Sequence of elements supporting sequential and parallel operations |
| Pipeline | A stream followed by one or more intermediate operations and a terminal operation |
| Intermediate Op | Returns a stream (e.g., filter, map) |
| Terminal Op | Produces a result or side-effect (e.g., collect, forEach) |
🔹 Stream Creation
Stream.of("a", "b");
Arrays.stream(new int[]{1, 2, 3});
List<String> list = List.of("a", "b");
list.stream(); // sequential
list.parallelStream(); // parallel🔄 Common Intermediate Operations
filter(Predicate<T>)
list.stream().filter(s -> s.length() > 3);map(Function<T, R>)
list.stream().map(String::toUpperCase);flatMap(Function<T, Stream<R>>)
List<List<String>> nested = List.of(List.of("a"), List.of("b"));
nested.stream().flatMap(List::stream);distinct(), sorted(), limit(n), skip(n)
list.stream().distinct().sorted().limit(10).skip(2);🔚 Terminal Operations
collect(...)
List<String> result = list.stream()
.filter(s -> s.length() > 2)
.collect(Collectors.toList());forEach(...)
list.stream().forEach(System.out::println);reduce(...)
int sum = numbers.stream().reduce(0, Integer::sum);count(), min(), max()
long count = list.stream().count();
Optional<String> shortest = list.stream().min(Comparator.comparingInt(String::length));🧰 Collectors Utility
Collectors.toList(), toSet(), toMap()
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(s -> s, String::length));joining()
String joined = list.stream().collect(Collectors.joining(", "));groupingBy(...)
Map<Integer, List<String>> grouped = list.stream()
.collect(Collectors.groupingBy(String::length));partitioningBy(...)
Map<Boolean, List<String>> partitioned = list.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 3));🧠 Advanced: Custom Collectors
Collector<String, ?, Set<String>> toSetCollector =
Collector.of(HashSet::new, Set::add, (left, right) -> { left.addAll(right); return left; });🔁 Parallel Streams
Be cautious: Only use if
-
CPU-bound tasks
-
No shared mutable state
-
Dataset is large enough
list.parallelStream()
.map(String::toUpperCase)
.collect(Collectors.toList());⚠️ Best Practices
-
Avoid side-effects in stream operations.
-
Don’t reuse streams – they are single-use.
-
Prefer method references when possible:
map(String::toUpperCase) -
Use
collect()for transformation, notforEach()with side-effects.
✅ Common Pitfalls
| Mistake | Fix |
|---|---|
| Reusing streams | Create new stream |
Using .parallel() blindly | Benchmark and analyze |
| Mixing stateful lambdas | Avoid; makes reasoning harder |
Using forEach to modify external collections | Use collect() instead |
🧪 Debugging Streams
Use .peek(...) for debugging:
list.stream()
.peek(System.out::println)
.map(String::toUpperCase)
.collect(Collectors.toList());🧭 Stream vs Loop
| Loop | Stream |
|---|---|
| More verbose | More declarative |
| Easy to debug | Harder to step through |
| Can mutate vars | Should avoid side-effects |
🛠️ Stream Utilities
Create Range
IntStream.range(0, 10); // 0 to 9
IntStream.rangeClosed(1, 5); // 1 to 5Infinite Streams
Stream.iterate(0, n -> n + 2).limit(10);
Stream.generate(Math::random).limit(5);📚 Real-World Patterns
1. Complex Grouping
Map<String, Map<Boolean, List<Person>>> result =
people.stream().collect(
Collectors.groupingBy(Person::getCity,
Collectors.partitioningBy(p -> p.getAge() > 30)));2. Custom Sorting
list.stream()
.sorted(Comparator.comparing(String::length).thenComparing(Comparator.naturalOrder()))
.toList();3. Stream a Map
map.entrySet().stream()
.filter(e -> e.getValue() > 10)
.map(Map.Entry::getKey);🔄 Functional Interfaces
| Interface | Signature |
|---|---|
Function<T,R> | T → R |
Predicate<T> | T → boolean |
Consumer<T> | T → void |
Supplier<T> | () → T |
BiFunction<T,U,R> | (T, U) → R |
📎 Tips for Reuse
-
Wrap common stream logic in utility methods.
-
Prefer composition over duplication.
-
Use descriptive variable/method names to convey pipeline intent.
Let me know if you’d like this formatted as a downloadable PDF, Markdown file, or Java snippet template.