Streams API in Java
Introduction
Java Streams API, introduced in Java 8, provides a powerful abstraction for processing collections of data in a declarative and functional style. Streams allow us to perform operations such as filtering, mapping, reducing, and collecting, making it easier to work with collections efficiently.
In this article, we will explore the Streams API in detail, including its features, benefits, operations, and real-world use cases.
What is a Stream?
A stream in Java is a sequence of elements that can be processed in a functional manner. Unlike collections, streams do not store data but operate on the source data such as arrays, lists, or I/O channels. Streams support lazy evaluation and allow method chaining to write concise and readable code.
Characteristics of Streams
-
Declarative: Express computations without specifying how they are executed.
-
Pipelining: Support method chaining for performing multiple operations efficiently.
-
Lazy Evaluation: Operations are performed only when a terminal operation is invoked.
-
Parallel Execution: Streams can be executed in parallel to enhance performance.
Creating Streams
Streams can be created from various data sources, including:
1. From Collections
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> stream = names.stream();
2. From Arrays
String[] array = {"Java", "Python", "C++"};
Stream<String> stream = Arrays.stream(array);
3. Using Stream.of()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
4. Generating Streams
Stream<Double> randomNumbers = Stream.generate(Math::random).limit(5);
5. Using Stream.iterate()
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2).limit(5);
Stream Operations
Stream operations are classified into two categories:
1. Intermediate Operations
These operations transform the stream into another stream. They are lazy and executed only when a terminal operation is invoked.
Filter
Filters elements based on a predicate:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().filter(n -> n % 2 == 0).forEach(System.out::println);
Map
Transforms elements using a function:
List<String> words = Arrays.asList("hello", "world");
words.stream().map(String::toUpperCase).forEach(System.out::println);
Sorted
Sorts elements in natural or custom order:
List<Integer> numbers = Arrays.asList(5, 3, 8, 1);
numbers.stream().sorted().forEach(System.out::println);
Distinct
Removes duplicate elements:
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4);
numbers.stream().distinct().forEach(System.out::println);
2. Terminal Operations
These operations consume the stream and produce a result.
ForEach
Applies a function to each element:
List<String> names = Arrays.asList("Alice", "Bob");
names.stream().forEach(System.out::println);
Collect
Collects elements into a collection:
List<String> names = Arrays.asList("Alice", "Bob");
List<String> uppercaseNames = names.stream().map(String::toUpperCase).collect(Collectors.toList());
Reduce
Performs a reduction operation:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum);
Count
Counts elements in a stream:
long count = Stream.of("A", "B", "C").count();
System.out.println(count);
Parallel Streams
Parallel streams enable concurrent execution for improved performance:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.parallelStream().forEach(System.out::println);
Real-World Use Case: Processing Employee Data
Consider an employee database where we filter employees earning more than $50,000 and collect their names:
class Employee {
String name;
double salary;
Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public double getSalary() { return salary; }
public String getName() { return name; }
}
public class StreamExample {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", 60000),
new Employee("Bob", 45000),
new Employee("Charlie", 75000)
);
List<String> highEarners = employees.stream()
.filter(e -> e.getSalary() > 50000)
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(highEarners);
}
}
Conclusion
Java Streams API provides a functional and concise approach to handling collections. By leveraging streams, developers can write expressive and efficient code. Whether filtering data, transforming elements, or performing aggregations, the Streams API enhances productivity and improves application performance.
Sign up here with your email
ConversionConversion EmoticonEmoticon