Master functional-style operations on Java collections using the Streams API - filtering, mapping, reducing, and parallel processing
Expert guidance for working with Java's Streams API (java.util.stream package) for functional-style collection processing, lazy evaluation, and parallel operations.
This skill helps you leverage Java Streams API to write concise, functional-style code for processing collections. You'll learn to chain operations like filter, map, and reduce, understand lazy evaluation, work with parallel streams, and avoid common pitfalls with stateful operations and non-interference requirements.
When the user asks for help with Java Streams, follow these guidelines:
Every stream operation follows this structure:
Remember: Intermediate operations are **lazy** (return a new stream), terminal operations are **eager** (produce a result).
Use primitive streams to avoid boxing overhead when working with numbers.
```java
// From collections
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
// From arrays
String[] array = {"a", "b", "c"};
Stream<String> streamFromArray = Arrays.stream(array);
// Static factory methods
Stream<String> streamOf = Stream.of("a", "b", "c");
IntStream range = IntStream.range(1, 10); // 1 to 9
Stream<String> generated = Stream.iterate("a", s -> s + "a").limit(5);
// From files
Stream<String> lines = Files.lines(Paths.get("file.txt"));
// From other sources
Stream<String> splitStream = Pattern.compile(",").splitAsStream("a,b,c");
IntStream randomInts = new Random().ints(10); // 10 random ints
```
**Filtering:**
```java
stream.filter(element -> condition)
```
**Mapping:**
```java
stream.map(element -> transformation)
stream.mapToInt(element -> intValue) // for primitive streams
stream.flatMap(element -> Stream.of(...)) // flatten nested streams
```
**Reducing:**
```java
stream.reduce(identity, (acc, element) -> acc + element)
stream.collect(Collectors.toList())
stream.collect(Collectors.joining(", "))
stream.collect(Collectors.groupingBy(classifier))
```
**Short-circuiting:**
```java
stream.findFirst()
stream.findAny()
stream.anyMatch(predicate)
stream.allMatch(predicate)
stream.limit(n)
```
**Stateless** (can process elements independently):
**Stateful** (may need to see all elements):
Stateful operations may require buffering data and multiple passes in parallel execution.
**When to use parallel streams:**
**When NOT to use parallel streams:**
```java
// Convert between sequential and parallel
stream.parallel() // switch to parallel
stream.sequential() // switch to sequential
stream.isParallel() // check current mode
```
**Critical:** Never modify the stream source during pipeline execution unless the source is a concurrent collection.
```java
// BAD - modifies source during execution
List<String> list = new ArrayList<>(Arrays.asList("a", "b"));
list.stream().forEach(s -> list.add(s + s)); // ConcurrentModificationException
// GOOD - collect results separately
List<String> list = new ArrayList<>(Arrays.asList("a", "b"));
List<String> doubled = list.stream()
.map(s -> s + s)
.collect(Collectors.toList());
```
Lambda expressions and method references passed to stream operations should be **stateless** (don't depend on mutable state that might change during execution).
```java
// BAD - stateful lambda
Set<Integer> seen = new HashSet<>();
stream.filter(e -> seen.add(e)); // depends on mutable state
// GOOD - use distinct() instead
stream.distinct();
```
Streams from I/O sources must be closed. Use try-with-resources:
```java
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
lines.filter(line -> line.startsWith("ERROR"))
.forEach(System.out::println);
}
```
**Sum example:**
```java
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(Widget::getWeight)
.sum();
```
**Grouping example:**
```java
Map<String, List<Widget>> byColor = widgets.stream()
.collect(Collectors.groupingBy(Widget::getColor));
```
**Find first match:**
```java
Optional<Widget> first = widgets.stream()
.filter(w -> w.getWeight() > 100)
.findFirst();
```
1. Reusing consumed streams (generate new stream instead)
2. Using stateful lambdas in parallel streams
3. Modifying source during execution
4. Using parallel streams for small datasets
5. Forgetting to close I/O streams
6. Boxing/unboxing overhead (use primitive streams)
7. Overusing streams for simple iterations (for-loop may be clearer)
Use streams when:
Don't force streams when:
Reference the official Java documentation for comprehensive details:
Always prioritize code readability and correctness over cleverness when applying the Streams API.
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/java-streams-api-reference/raw