Introduction
The Streams API introduced in Java 8 offers a new way to process data in collections (like lists and sets) with simple, readable code. It allows developers to perform complex data processing operations on collections using a functional programming approach. In this blog, we will explore the key concepts of the Stream API, including intermediate and terminal operations, and parallel stream processing.
Prerequisites
- Basic Java Knowledge
- Java 8 features
- IntelliJ IDEA or Any other IDE
What is Stream API?
In Java, a stream is a sequence of elements that can be generated using various operations. Streams can be created using collections, arrays, or I/O channels. Stream operations are divided into two categories: intermediate and terminal.
1. Intermediate operations
Intermediate operations change one stream into another. They are lazy, which means they do not execute any processing until a terminal operation is initiated.
2. Terminal operations
Terminal operations produce a result or a side effect. Once a terminal operation is completed, the stream is considered consumed and cannot be reused.
Stream API operations
1. Intermediate operation
filter
The filter method is used to select elements that match the given predicate.
Example
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Apple", "Banana", "Mango", "Chhery", "Chikoo");
List filteredNames =
names.stream().filter(name -> name.startsWith("C")).collect(Collectors.toList());
System.out.println("Using Filter method fruit name start with C :" + filteredNames);
}
}
//Output:
Using Filter method fruit name start with C :[Chhery, Chikoo]
map
The map method is used to transform each element of the stream using the provided function.
Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Apple", "Banana", "Mango", "Chhery", "Chikoo");
List nameLength =
names.stream().map(String::length)
.collect(Collectors.toList());
System.out.println("Length of names in the list : " + nameLength);
}
}
//Output:
Length of names in the list : [5, 6, 5, 6, 6]
flatMap
The flatMap method is used to combine a stream of collections into a single stream.
Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List> names =
Arrays.asList(Arrays.asList("Apple", "Banana"), Arrays.asList("Mango", "Watermelon"));
List flattenedList = names.stream().flatMap(List::stream).collect(Collectors.toList());
System.out.println("Combined two list into a single list :" + flattenedList);
}
}
//Output:
Combined two list into a single list :[Apple, Banana, Mango, Watermelon]
distinct
The distinct method is used to remove duplicate elements from the stream.
Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1,2,2,3,4,5,2,10,1,7,5,2);
List distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Remove all duplicate numbers in the list : "+distinctNumbers);
}
}
//Output:
Remove all duplicate numbers in the list : [1, 2, 3, 4, 5, 10, 7]
sorted
The sorted method is used to sort elements of the stream.
Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Chhaya", "Mahi", "Alpa", "Zainab", "Radha");
List sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Sorted elements list : "+sortedNames);
}
}
Output:
Sorted elements list : [Alpa, Chhaya, Mahi, Radha, Zainab]
peek
The peek method is used for debugging purposes. It allows us to perform an action on each element of the stream without modifying it.
Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Chhaya", "Mahi", "Alpa", "Zainab", "Radha");
List result = names.stream().peek(System.out::println).collect(Collectors.toList());
System.out.println("Print elements : " + result);
}
}
//Output:
Print elements : [Chhaya, Mahi, Alpa, Zainab, Radha]
limit
The limit method is used to truncate the stream to a specified number of elements.
Example:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
List limitedNumbers = numbers.stream().limit(3).collect(Collectors.toList());
System.out.println("Result : " + limitedNumbers);
}
}
//Output:
Result : [1, 2, 3]
skip
The skip method skips the first n elements of the stream.
Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
List skippedNumbers = numbers.stream().skip(2).collect(Collectors.toList());
System.out.println("Result : "+skippedNumbers);
}
}
//Output:
Result : [3, 4, 5]
2. Terminal operation
forEach
The forEach method is used to perform an action for each element of the stream.
Example
import java.util.Arrays;
import java.util.List;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Chhaya", "Mahi", "Alpa", "Zainab", "Radha");
names.stream().forEach(System.out::println);
}
}
//Output:
Chhaya
Mahi
Alpa
Zainab
Radha
collect
The collect method is used to add elements into collections or other data structures.
Example
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Chhaya", "Mahi", "Alpa", "Zainab", "Radha");
List collectedNames = names.stream()
.collect(Collectors.toList());
System.out.println("Result : "+collectedNames);
}
}
//Output:
Result : [Chhaya, Mahi, Alpa, Zainab, Radha]
reduce
The reduce method is used to combine the elements of the stream into a single result using a binary operator.
Example
import java.util.Arrays;
import java.util.List;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println("Sum of numbers : " + sum);
}
}
//Output:
Sum of numbers : 15
allMatch
The allMatch method checks if all elements of the stream match a given predicate.
Example
import java.util.Arrays;
import java.util.List;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);
System.out.println(allEven);
}
}
//Output:
false
anyMatch
The anyMatch method checks if any element of the stream matches a given predicate.
Example
import java.util.Arrays;
import java.util.List;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0);
System.out.println(anyEven);
}
}
//Output:
true
noneMatch
The noneMatch method checks if no elements of the stream match a given predicate.
Example
import java.util.Arrays;
import java.util.List;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean noneNegative = numbers.stream()
.noneMatch(n -> n < 0);
System.out.println(noneNegative);
}
}
//Output:
true
findFirst
The findFirst method returns the first element of the stream, if any.
Example
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Chhaya", "Mahi", "Alpa", "Zainab", "Radha");
Optional first = names.stream().findFirst();
System.out.println(first);
}
}
//Output:
Optional[Chhaya]
findAny
The findAny method returns any element of the stream, which is useful in parallel streams.
Example
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Chhaya", "Mahi", "Alpa", "Zainab", "Radha");
Optional any = names.stream()
.findAny();
System.out.println(any);
}
}
//Output:
Optional[Chhaya]
max and min
The max and min methods return the maximum and minimum elements of the stream, respectively, based on a comparator.
Example
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(11, 22, 33, 44, 55);
Optional max = numbers.stream()
.max(Integer::compare);
System.out.println(max);
Optional min = numbers.stream()
.min(Integer::compare);
System.out.println(min);
}
}
//Output:
Optional[55]
Optional[11]
toArray
The toArray method converts the stream into an array.
Example
import java.util.Arrays;
import java.util.List;
public class StreamApiExample {
public static void main(String[] args) {
List names = Arrays.asList("Chhaya", "Mahi", "Alpa", "Zainab", "Radha");
String[] namesArray = names.stream().toArray(String[]::new);
System.out.println(Arrays.toString(namesArray));
}
}
//Output:
[Chhaya, Mahi, Alpa, Zainab, Radha]
3. Parallel
The Java Stream API also allows parallel processing, which can significantly improve performance for large datasets. To create a parallel stream, We can call the parallelStream() method on a collection.
Example
import java.util.Arrays;
import java.util.List;
public class StreamApiExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1,2,3,4,5);
int sum = numbers.parallelStream().reduce(0,Integer::sum);
System.out.println("Sum of numbers : "+sum);
}
}
//Output:
Sum of numbers : 15
Conclusion
The Java Stream API provides a powerful and flexible way to process collections and data sequences. By understanding the various intermediate and terminal operations, and the benefits and caveats of parallel streams, we can write more efficient and readable code. Whether we’re filtering, mapping, or reducing data that can enhance our Java Application.