Operation | Description | Input Data Type | Example |
---|---|---|---|
filter(Predicate) |
Filters elements of the stream based on the given predicate | Predicate | stream.filter(x -> x > 5) |
map(Function) |
Transforms each element in the stream using the provided function | Function | stream.map(x -> x * 2) |
flatMap(Function) |
Transforms each element in the stream into a stream of elements and then flattens them into a single stream | Function | stream.flatMap(line -> Arrays.stream(line.split("\\s+"))) |
distinct() |
Removes duplicate elements from the stream | Any | stream.distinct() |
sorted() |
Sorts the elements of the stream in natural order | Comparable | stream.sorted() |
sorted(Comparator) |
Sorts the elements of the stream using the provided comparator | Comparator | stream.sorted(Comparator.reverseOrder()) |
limit(long) |
Truncates the stream to a maximum size of n elements |
Any | stream.limit(10) |
skip(long) |
Skips the first n elements of the stream |
Any | stream.skip(5) |
peek(Consumer) |
Performs a side-effect for each element of the stream without changing its contents | Consumer | stream.peek(System.out::println) |
parallel() |
Indicates that subsequent operations should be executed in parallel | Any | stream.parallel() |
sequential() |
Indicates that subsequent operations should be executed sequentially, overriding parallel mode if set previously | Any | stream.sequential() |
Operation | Description | Input Data Type | Example |
---|---|---|---|
forEach(Consumer) |
Performs an action for each element of the stream | Consumer | stream.forEach(System.out::println) |
collect(Collector) |
Performs a mutable reduction operation on the elements of the stream using a collector | Collector | List<Integer> list = stream.collect(Collectors.toList()) |
toArray() |
Converts the elements of the stream into an array | Any | Object[] arr = stream.toArray() |
reduce(BinaryOperator) |
Performs a reduction on the elements of the stream using the provided binary operator | BinaryOperator | Optional<Integer> sum = stream.reduce(Integer::sum) |
min(Comparator) |
Finds the minimum element in the stream according to the provided comparator | Comparator | Optional<Integer> min = stream.min(Comparator.naturalOrder()) |
max(Comparator) |
Finds the maximum element in the stream according to the provided comparator | Comparator | Optional<Integer> max = stream.max(Comparator.reverseOrder()) |
count() |
Returns the count of elements in the stream as a long |
Any | long count = stream.count() |
anyMatch(Predicate) |
Checks if any element of the stream matches the given predicate | Predicate | boolean anyMatch = stream.anyMatch(x -> x > 5) |
allMatch(Predicate) |
Checks if all elements of the stream match the given predicate | Predicate | boolean allMatch = stream.allMatch(x -> x > 5) |
noneMatch(Predicate) |
Checks if none of the elements of the stream match the given predicate | Predicate | boolean noneMatch = stream.noneMatch(x -> x > 5) |
findAny() |
Returns an arbitrary element of the stream (if present) | Any | Optional<Integer> any = stream.findAny() |
findFirst() |
Returns the first element of the stream (if any) | Any | Optional<Integer> first = stream.findFirst() |
Important
Points To remember
-
Once Consumed, Cannot Be Reused: Streams can be used only once. Once a terminal operation is performed, the stream is considered consumed and cannot be reused. Create a new stream for each sequence of operations.
List<String> myList = Arrays.asList("a", "b", "c"); Stream<String> stream = myList.stream(); stream.forEach(System.out::println); // Consumes the stream stream.collect(Collectors.toList()); // IllegalStateException
-
Intermediate Operations Are Lazy: Intermediate operations (e.g.,
map
,filter
) are not executed until a terminal operation (e.g.,collect
,forEach
) is invoked. This allows for optimized processing and the possibility of short-circuiting.List<String> myList = Arrays.asList("a", "b", "c"); myList.stream() .filter(s -> { System.out.println("filtering: " + s); return s.startsWith("a"); }) .map(s -> { System.out.println("mapping: " + s); return s.toUpperCase(); }); // No output because no terminal operation is invoked
-
Avoid Stateful Intermediate Operations: Use stateless intermediate operations (e.g.,
map
,filter
) instead of stateful ones (e.g.,distinct
,sorted
), especially in parallel streams, to avoid performance issues and unpredictable results.Stream.of("a", "b", "c", "a").distinct(); // Stateful
-
Use Parallel Streams Judiciously: Parallel streams can improve performance for CPU-intensive operations on large data sets, but they introduce overhead and potential thread-safety issues. Measure performance and ensure thread safety.
myList.parallelStream().forEach(System.out::println);
-
Avoid Side Effects in Stream Operations: Stream operations should be free of side effects. Use
forEach
for actions with side effects and prefer immutable data structures or thread-safe mechanisms when dealing with shared state.// Avoid modifying external state in intermediate operations List<String> results = new ArrayList<>(); myList.stream().map(s -> { results.add(s); // Side effect, not recommended return s.toUpperCase(); });
-
Consider Order and Stability: Streams have inherent ordering (e.g., lists, sorted sets). Ensure operations that depend on order (e.g.,
forEachOrdered
,sorted
) maintain the desired order.Stream.of("a", "b", "c").forEachOrdered(System.out::println); // Ordered
-
Pipeline Structure Matters: Structure the stream pipeline to optimize performance. Place operations that reduce the number of elements (e.g.,
filter
) before operations that process each element (e.g.,map
).myList.stream() .filter(s -> s.startsWith("a")) // Reduces elements .map(String::toUpperCase) // Processes remaining elements .collect(Collectors.toList());
-
Choose Appropriate Stream Sources: Streams can be created from various sources (collections, arrays, generators). Ensure the source is appropriate for the intended operations and performance requirements.
Stream<String> stream = Stream.of("a", "b", "c"); // Stream from values
-
Close Resources: Ensure that streams that wrap IO resources (e.g., files) are closed properly. Use try-with-resources to manage resources automatically.
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) { lines.forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); }
-
Understand Terminal Operations: Terminal operations trigger the processing of the stream and produce a result or side effect. Common terminal operations include
collect
,forEach
,reduce
,count
, andfindAny
.long count = myList.stream().filter(s -> s.startsWith("a")).count();
By keeping these points in mind, you can effectively leverage the Stream API in Java 8 to write clean, efficient, and maintainable code. Properly structuring stream operations, being mindful of performance considerations, and avoiding common pitfalls will help you make the most out of this powerful feature.