A stream in Java is a sequence of elements from a source that supports various aggregate operations. It is not a data structure itself but rather a way to process data from existing data structures like collections or arrays. Streams allow you to perform operations on the elements in a declarative way, similar to SQL queries on a database.
Streams can be created from different sources, including:
Collection
implementation such as List
, Set
, etc.filter()
, map()
, and sorted()
.forEach()
, collect()
, and reduce()
.Here are some common ways to create streams:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamCreationFromCollection {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
Stream<String> stream = list.stream();
}
}
import java.util.stream.Stream;
public class StreamCreationFromArray {
public static void main(String[] args) {
String[] array = {"apple", "banana", "cherry"};
Stream<String> stream = Stream.of(array);
}
}
The filter()
method is used to select elements that match a given predicate.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> filteredStream = numbers.stream().filter(n -> n % 2 == 0);
filteredStream.forEach(System.out::println);
}
}
The map()
method is used to transform each element of the stream using a given function.
import java.util.Arrays;
import java.util.List;
public class MapExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream().map(n -> n * 2).forEach(System.out::println);
}
}
The collect()
method is used to accumulate the elements of the stream into a collection or other data structures.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CollectExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers);
}
}
The reduce()
method is used to combine the elements of the stream into a single value.
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class ReduceExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
sum.ifPresent(System.out::println);
}
}
Filtering is a common operation when working with streams. You can use the filter()
method to select elements based on a certain condition. For example, to filter a list of employees based on their age:
import java.util.ArrayList;
import java.util.List;
class Employee {
private int age;
public Employee(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
public class EmployeeFiltering {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(25));
employees.add(new Employee(30));
employees.add(new Employee(40));
List<Employee> seniorEmployees = employees.stream()
.filter(e -> e.getAge() > 30)
.collect(java.util.stream.Collectors.toList());
System.out.println(seniorEmployees.size());
}
}
Mapping is useful when you need to transform the elements of a stream. For example, to convert a list of strings to a list of their lengths:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StringLengthMapping {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths);
}
}
Reducing is used to combine the elements of a stream into a single value. For example, to find the sum of all elements in a list:
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class SumReduction {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
sum.ifPresent(System.out::println);
}
}
The sorted()
method is used to sort the elements of a stream.
import java.util.Arrays;
import java.util.List;
public class SortingExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2);
numbers.stream().sorted().forEach(System.out::println);
}
}
Intermediate operations in streams are lazily evaluated. This means that they are not executed until a terminal operation is called. This can lead to significant performance improvements, especially when dealing with large data sets. For example, if you have a long stream and you apply multiple intermediate operations, the operations are combined and executed only when a terminal operation is invoked.
Java Streams API supports parallel processing through parallel streams. You can convert a sequential stream to a parallel stream using the parallel()
method. Parallel streams can significantly speed up the processing of large data sets by distributing the work across multiple threads. However, you need to be careful when using parallel streams as they have some overhead and may not always result in better performance, especially for small data sets.
import java.util.Arrays;
import java.util.List;
public class ParallelStreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();
System.out.println(sum);
}
}
It is recommended to avoid side - effects in stream operations. Side - effects can make the code harder to understand and debug. For example, modifying a shared variable inside a forEach()
method is a side - effect and should be avoided. Instead, use pure functions that do not modify external state.
The Java Streams API is a powerful tool for processing collections of data in a declarative and efficient way. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can write more concise, readable, and maintainable code. Whether you are working with small or large data sets, the Streams API provides a flexible and scalable solution for data processing.