UNIT 5
ADVANCED STREAM FEATURES
AND
REGULAR EXPRESSIONS
Importance and Use Cases of Advanced Stream Features
• Efficiency and Performance: Streams allow for efficient data processing by
enabling operations like filtering, mapping, and reducing without the need for
explicit loops.
• Parallel Processing: Streams support parallel execution, which can
significantly improve performance for large datasets by leveraging multi-core
processors.
• Functional Programming
: Streams promote a functional programming style, which can lead to more
predictable and maintainable code by avoiding mutable state and side effects
• Expressiveness:
The Stream API provides a rich set of operations that can be combined in a fl
uent manner,
making the code more expressive and easier to understand
Use Cases of Advanced Stream Features
• Data Transformation: Streams can be used to
transform data from one form to another. For example,
converting a list of stings to uppercase:
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
• Filtering Data: Streams make it easy to filter data based on certain criteria.
For instance, filtering out even numbers from a list:
List<Integer> oddNumbers = numbers.stream()
.filter(n -> n % 2 != 0)
.collect(Collectors.toList());
• Aggregation Operations: Streams support aggregation operations like
sum, average, and count. For example, calculating the sum of a list of
integers:
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
• Parallel Processing: Streams can be processed in parallel to improve
performance. For example, summing a large list of integers in parallel:
int parallelSum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
• Complex Data Pipelines: Streams can be used to create complex data processing
pipelines. For example, filtering, mapping, and collecting data in a single pipeline:
List<String> result = data.stream()
.filter(d -> d.startsWith("A"))
.map(String::toLowerCase)
.collect(Collectors.toList());
Java's stream API
• Simplified Data Processing
Importance: Advanced stream features simplify complex data processing tasks, making the code more readable and
maintainable by abstracting away the boilerplate code.
Use Cases:
Data Transformation: Converting a list of objects into another form, such as mapping user entities to DTOs (Data
Transfer Objects).
Filtering Data: Easily filtering large datasets based on specific criteria, such as finding all active users or filtering
transactions above a certain threshold.
• Parallel Processing
Importance: Streams can be processed in parallel to leverage multi-core processors, which improves performance
when dealing with large data sets.
Use Cases:
Batch Processing: Handling large batches of data, such as processing millions of records in a database.
Big Data: Performing complex computations over large datasets, like summing up values or aggregating
statistics.
• Functional Programming Integration
Importance: Streams provide a functional programming approach to data processing,
which allows for more expressive and concise code.
Use Cases: Event Handling: Using streams in event-driven systems to handle and
process events functionally.
Custom Collectors: Creating custom collectors for complex data aggregations, such as
grouping and partitioning data.
• Enhanced Debugging and Testing
Importance: Advanced stream features, such as peek and custom collectors, help in
debugging and testing stream operations by providing intermediate operation insights.
Use Cases: Logging: Using peek() to log stream elements as they are processed, which
aids in debugging complex streams.
Testable Pipelines: Breaking down stream operations into smaller, testable units,
making it easier to verify the correctness of each stage.
• Stream Customization and Extension
Importance: Java allows customization and extension of stream
behavior through custom collectors, spliterators, and more, which
increases the flexibility of the stream API.
• Use Cases:
Custom Collectors: Implementing custom collectors for specific
aggregation needs, such as collecting statistics with custom rules.
Spliterators: Creating custom spliterators for non-standard data
sources, like iterating over data structures that aren't inherently
sequential.
Creating Custom Streams
Creating custom streams in Java involves defining a stream from
non-standard data sources or operations that are not directly
supported by the standard Stream API.
This process typically involves the use of
• Spliterator
• Stream.Builder
• Stream.generate methods.
Steps to Create Custom Streams in Java
1. Using Stream.of() and Stream.Builder()
Stream.of(): Creates a stream from a specified set of
elements.
Stream.Builder: Useful when you need to build a stream step
by step.
Stream<String> stream = Stream.of("apple", "banana",
"cherry");
Stream<String> builtStream =
2. Using Stream.generate()
Stream.generate(): Creates an infinite stream where each element
is generated using a Supplier function.
Stream<Double>randomNumbers=Stream.generate(Math::random).li
mit(10);
3. Using Stream.iterate()
Stream.iterate(): Creates an infinite stream where each element is
produced by applying a function to the previous element.
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n +
2).limit(10);
4. Creating Streams from Custom@Override
Data Structures public boolean
class CustomSpliterator implementstryAdvance(Consumer<? super
Spliterator<Integer> { Integer> action) {
private final int[] array; if (index < array.length) {
private int index = 0; action.accept(array[index++]);
public CustomSpliterator(int[] array) return true;
{ }
this.array = array; return false;
} }
@Override @Override
public Spliterator<Integer> trySplit() { public int characteristics() {
int length = array.length - index; return SIZED | SUBSIZED | ORDERED;
if (length <= 1) return null; }
int splitIndex = length / 2 + index; }
Spliterator<Integer> spliterator = new public class CustomStream {
CustomSpliterator(Arrays.copyOfRange(array, public static void main(String[] args) {
index, splitIndex)); int[] array = {1, 2, 3, 4, 5};
index = splitIndex; Spliterator<Integer> spliterator = new
return spliterator; CustomSpliterator(array);
} Stream<Integer> stream =
@Override StreamSupport.stream(spliterator, false);
public long estimateSize() { stream.forEach(System.out::println);
• }
return array.length - index; • }
}
INFINITE STREAMS