When working with Java, understanding and knowing how to use streams to optimize work performance is necessary. Today I will show you how to use some of the functions that Java streams provide, helping Your code become more visible and elegant.
First, let’s go through the popular functions that Java Stream provides.
filter removes elements that do not satisfy the predicate filter condition, or in other words, retains elements that satisfy the filter condition
import java.util.Arrays;
import java.util.stream.Collectors;
public class CafeincodeExample {
public static void main(String[] args) {
var domains = Arrays.asList("cafeincode", "medium", "google");
var filtered = domains.stream()
.filter(item -> item.startsWith("c"))
.peek(item -> System.out.println("Result: " + item))
.collect(Collectors.toList());
}
}
Result: cafeincode
map is responsible for mapping each element in the stream to another data type through the function you specify and creating a new stream, like in the example below, I use the available toUpperCase function
import java.util.Arrays;
import java.util.stream.Collectors;
public class CafeincodeExample {
public static void main(String[] args) {
var input = Arrays.asList("cafeincode", "medium", "google");
var mapped = input.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Result: " + mapped);
}
}
Result: [CAFEINCODE, MEDIUM, GOOGLE]
flatMap is used to process the elements of a stream and help transform them into a new stream or a list of elements, i.e. it will combine child streams into a parent stream.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CafeincodeExample {
public static void main(String[] args) {
var nestedNumbers = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
var flattenedNumbers = nestedNumbers.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Result: " + flattenedNumbers);
}
}
Result: [1, 2, 3, 4, 5, 6]
distinct is used to remove duplicate elements from a stream and it will return a new stream containing only unique elements
import java.util.Arrays;
import java.util.stream.Collectors;
public class CafeincodeExample {
public static void main(String[] args) {
var numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5, 5);
var distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Result: " + distinctNumbers);
}
}
Result: [1, 2, 3, 4, 5]
sorted is used to sort the elements of a stream in a certain order and will return a new stream containing the sorted elements
import java.util.Arrays;
import java.util.stream.Collectors;
public class CafeincodeExample {
public static void main(String[] args) {
var numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);
var sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Result: " + sortedNumbers);
}
}
Result: [1, 1, 2, 3, 4, 5, 5, 6, 9]
peek is often used to perform debugging or logging operations on elements during stream processing without changing the content of the stream.
import java.util.stream.IntStream;
public class CafeincodeExample {
public static void main(String[] args) {
IntStream.range(1, 6)
.peek(element -> System.out.println("Processing element: " + element))
.map(CafeincodeExample::mapping)
.forEach(System.out::println);
}
private static Integer mapping(Integer input) {
return input * input;
}
}
Processing element: 1
1
Processing element: 2
4
Processing element: 3
9
Processing element: 4
16
Processing element: 5
25
limit is used to limit the number of elements in a stream, it will return a new stream containing the number of elements limited to a certain value.
import java.util.stream.IntStream;
public class CafeincodeExample {
public static void main(String[] args) {
IntStream.range(1, 100)
.limit(5)
.forEach(System.out::println);
}
}
1
2
3
4
5
skip is used to skip a certain number of elements in a stream and return a new stream starting from the position that was skipped
import java.util.stream.IntStream;
public class CafeincodeExample {
public static void main(String[] args) {
IntStream.range(1, 11)
.skip(5)
.forEach(System.out::println);
}
}
6
7
8
9
10
In this example, IntStream.range(1, 11)create a Stream containing the numbers 1 to 10.
- skip(5)used to skip the first 5 elements of the stream
- forEach(System.out::println)used to print out the remaining elements of the stream, starting from the 6th element to the last element
toArray is used to convert a stream into an array, this method returns an array containing the elements of the stream in order.
import java.util.stream.IntStream;
public class CafeincodeExample {
public static void main(String[] args) {
int[] numbers = IntStream.range(1, 6)
.toArray();
for (int number : numbers) {
System.out.println("Result: " + number);
}
}
}
Result: 1
Result: 2
Result: 3
Result: 4
Result: 5
reduce is used to perform a transformation on the elements of the stream to calculate a final value
import java.util.Arrays;
public class CafeincodeExample {
public static void main(String[] args) {
Integer[] integers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
var result = Arrays.stream(integers).reduce(0, Integer::sum);
System.out.println("Result: " + result);
}
}
Result: 55
collect is used to collect the elements of a stream into a specific data structure, such as a List, Set or Map
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
var list = stream.collect(Collectors.toList());
System.out.println("Result: " + list);
}
}
Result: [1, 2, 3, 4, 5]
count is used to count the number of elements in a stream, this method returns an integer value that is the number of elements in the stream
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 11);
var count = stream.count();
System.out.println("Result: " + count);
}
}
Result: 10
anyMatch is used to check whether at least one element in the stream satisfies the condition
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of("apple", "banana", "orange", "grape", "kiwi");
không
var anyMatch = stream.anyMatch(str -> str.startsWith("a"));
if (anyMatch) {
System.out.println("There are elements starting with the letter 'a' in Stream");
} else {
System.out.println("There are no elements starting with the letter 'a' in Stream");
}
}
}
There are elements starting with the letter 'a' in Stream
allMatch is used to check whether all elements in the stream satisfy the given condition
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of(2, 4, 6, 8, 10);
var allMatch = stream.allMatch(number -> number % 2 == 0);
if (allMatch) {
System.out.println("Result: All numbers in the Stream are divisible by 2");
} else {
System.out.println("Result: There is at least one number in the Stream that is not divisible by 2");
}
}
}
Result: All numbers in the Stream are divisible by 2
noneMatch it still returns a boolean value, however, this function will check that all elements in the stream must not satisfy a condition.
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of(2, 4, 6, 8, 10);
var noneMatch = stream.noneMatch(number -> number % 5 == 0);
if (noneMatch) {
System.out.println("Result: There are no numbers in the Stream that are divisible by 5");
} else {
System.out.println("Result: There is at least one number in the Stream that is divisible by 5");
}
}
}
Result: There is at least one number in the Stream that is divisible by 5
findFirst returns the first element in the stream
import java.util.Optional;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of("apple", "banana", "cherry", "avocado", "blueberry");
Optional<String> firstElement = stream.findFirst();
if (firstElement.isPresent()) {
System.out.println("Result: First element: " + firstElement.get());
} else {
System.out.println("Result: Stream is empty.");
}
}
}
Result: First element: apple
findAny returns any element in the stream
import java.util.Optional;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of("apple", "banana", "cherry", "avocado", "blueberry");
Optional<String> anyElement = stream.findAny();
if (anyElement.isPresent()) {
System.out.println("Result: Any element: " + anyElement.get());
} else {
System.out.println("Result: Stream is empty.");
}
}
}
Result: Any element: apple
min returns the smallest word in the stream
import java.util.Optional;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of(5, 2, 8, 1, 3);
Optional<Integer> minElement = stream.min(Integer::compareTo);
if (minElement.isPresent()) {
System.out.println("Minimum element: " + minElement.get());
} else {
System.out.println("Stream is empty.");
}
}
}
Minimum element: 1
max returns the largest element in the stream
import java.util.Optional;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of(5, 2, 8, 1, 3);
Optional<Integer> maxElement = stream.max(Integer::compareTo);
if (maxElement.isPresent()) {
System.out.println("Maximum element: " + maxElement.get());
} else {
System.out.println("Stream is empty.");
}
}
}
Maximum element: 8
groupingBy is used to group elements in the stream according to a certain condition
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
var stream = Stream.of("apple", "banana", "cherry", "avocado", "blueberry");
var groupedByLength = stream.collect(Collectors.groupingBy(String::length));
System.out.println("Result: " + groupedByLength);
}
}
Result: {5=[apple], 6=[banana, cherry], 7=[avocado], 9=[blueberry]}
partitioningBy is used to divide the stream elements into two groups based on a provided condition, the return result is a Map with two keys: true and false
Elements that satisfy the condition will be assigned to the key true and elements that do not satisfy the condition will be assigned to the key false
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Map<Boolean, List<Integer>> partitioned = stream.collect(Collectors.partitioningBy(i -> i % 2 == 0));
System.out.println("Even numbers: " + partitioned.get(true));
System.out.println("Odd numbers: " + partitioned.get(false));
}
}
Even numbers: [2, 4, 6, 8, 10]
Odd numbers: [1, 3, 5, 7, 9]
joining is used to combine stream elements into one string, you can provide a separating string between elements or leave it as default
import java.util.Arrays;
import java.util.stream.Collectors;
public class CafeincodeExample {
public static void main(String[] args) {
var data = Arrays.asList("apple", "banana", "cherry");
String result = data.stream().collect(Collectors.joining(", "));
System.out.println("Result: " + result);
}
}
Result: apple, banana, cherry
Iterating is often used when it is necessary to create a sequence of values generated according to a certain rule
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> evenNumbers = Stream.iterate(2, n -> n + 2).limit(5);
evenNumbers.forEach(System.out::println);
}
}
2
4
6
8
10
of is used to create a stream from the elements provided as arguments
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("apple", "banana", "cherry");
stream.forEach(System.out::println);
}
}
apple
banana
cherry
concat is used to concatenate two streams together, creating a new stream containing all the elements of the two original streams
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("apple", "banana");
Stream<String> stream2 = Stream.of("cherry", "grape");
Stream<String> concatenatedStream = Stream.concat(stream1, stream2);
concatenatedStream.forEach(System.out::println);
}
}
apple
banana
cherry
grape
unordered is used to specify that the stream will not follow the originally defined order.
This means that elements in the stream can appear in any order after performing transformations or collecting on the stream
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> unorderedStream = stream.unordered();
unorderedStream.forEach(System.out::println);
}
}
1
2
3
4
5
range is used to create a stream containing consecutive integers, starting at a value A and ending at a value B-1
import java.util.stream.IntStream;
public class CafeincodeExample {
public static void main(String[] args) {
IntStream rangeStream = IntStream.range(1, 6);
rangeStream.forEach(System.out::println);
}
}
1
2
3
4
5
rangeClosed is used to create a stream with integers in the range A to B
import java.util.stream.IntStream;
public class CafeincodeExample {
public static void main(String[] args) {
IntStream rangeClosedStream = IntStream.rangeClosed(1, 5);
rangeClosedStream.forEach(System.out::println);
}
}
1
2
3
4
5
generate is used to generate a stream by generating elements based on a provided supplier, which independently generates elements each time it is called
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<String> stream = Stream.generate(() -> "Cafeincode").limit(3);
stream.forEach(System.out::println);
}
}
Cafeincode
Cafeincode
Cafeincode
takeWhile is used to retrieve elements from a stream until a condition is no longer satisfied.
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> takenStream = stream.takeWhile(n -> n <= 7);
takenStream.forEach(System.out::println);
}
}
1
2
3
4
5
6
7
In contrast to takeWhile, dropWhile is used to remove elements from a stream until a condition is no longer satisfied, the remainder of the stream will be elements that do not satisfy the condition
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> droppedStream = stream.dropWhile(n -> n <= 6);
droppedStream.forEach(System.out::println);
}
}
7
8
9
10
boxed is used to convert the elements of a stream from primitive types to boxed types, which is useful when you want to work with streams of objects instead of primitive types
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
IntStream intStream = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);
Stream<Integer> boxedStream = intStream.boxed();
boxedStream.forEach(System.out::println);
}
}
1
2
3
4
5
6
7
8
parallel used to convert a stream into a stream that can be processed in parallel.
When a stream is processed in parallel, elements of the stream can be processed on multiple threads simultaneously, increasing application performance on systems with multiple processors.
However, in reality, using parallel in some cases does not really bring much significant performance, you can learn more about this part yourself.
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> parallelStream = stream.parallel();
parallelStream.forEach(System.out::println);
}
}
3
5
4
2
1
sequential used to convert a stream from parallel processing to sequential processing.
When a stream is processed sequentially, the stream’s elements are processed in order from start to finish on a single stream.
import java.util.stream.Stream;
public class CafeincodeExample {
public static void main(String[] args) {
Stream<Integer> parallelStream = Stream.of(1, 2, 3, 4, 5).parallel();
Stream<Integer> sequentialStream = parallelStream.sequential();
sequentialStream.forEach(System.out::println);
}
}
1
2
3
4
5
Best practice in using Java streams
Using streams properly makes your code more elegant, easier to see, and neater than the traditional coding style.
However, it must be said again and again, that too much of anything is not necessarily good. Overusing streams or confusing writing methods will also give you a real headache every time you debug.
So below are some best practices that I think should be applied to both make good use of it and avoid unnecessary abuse.
- When using a stream where multiple methods are continuously applied, put each function on a different line, it will be extremely useful when debugging.
- Use the methods I listed above map(), filter(), reduce(), collect(),... appropriately to perform operations on the stream
- Check for null during map and filter operations
- Avoid overusing parallel in the coding process, in many cases it does not achieve as much performance value as you think. If possible, just use the default sequential only.
- Name variables when used appropriately. Do not use variable names with the default letters a, b, and c because it will be difficult to understand when reading.
- Using Optional makes sense in cases where findFirst or findAny are used
- In reality, there will be many methods you need to re-implement rather than using the default, for example, sorted
- Use Peek to debug properly
- In the case of converting List to Map , you need to be careful to pay attention to duplicate keys
- Java streams use lazy evaluation (discussed in another article), which means elements are only calculated when necessary. You can use this to increase performance by avoiding unnecessary calculations.
Thanks, before you go:
👏 Please clap for the story and follow the author 👉
Please share your questions or insights in the comments section below.
Let’s connect on LinkedIn
Originally published at https://cafeincode.com on April 17, 2024.
Top comments (0)