Exploring Advanced Java Stream API Concepts

Murali krishna Konduru
2 min readMay 26, 2024

--

Java Streams API, introduced in Java 8, revolutionized the way we process collections of objects. Streams provide a declarative way to manipulate data, allowing for more readable and maintainable code. In this article, we delve into some advanced features and concepts of the Streams API, focusing on the following:

  1. Stream.ofNullable
  2. Stream.iterate
  3. Collectors.collectingAndThen
  4. Stream.takeWhile and Stream.dropWhile
  5. Collectors.teeing
  6. Stream.concat
  7. Collectors.partitioningBy
  8. IntStream for Ranges

1. Stream.ofNullable

Stream.ofNullable is a method that allows us to create a stream from a single element that might be null. This is useful for avoiding NullPointerException and can streamline handling optional values in streams.

String value = null;
Stream<String> stream = Stream.ofNullable(value);
stream.forEach(System.out::println); // This will print nothing as the stream is empty.

2. Stream.iterate

Stream.iterate generates an infinite sequential ordered stream produced by iterative application of a function. This method can also take a predicate to stop the iteration.

Stream.iterate(0, n -> n < 10, n -> n + 2)
.forEach(System.out::println); // Prints 0, 2, 4, 6, 8

3. Collectors.collectingAndThen

Collectors.collectingAndThen is used to perform a finishing transformation on the result of another collector. This is particularly useful when we need to transform the result after collecting it.

List<String> list = Arrays.asList("a", "b", "c");
List<String> unmodifiableList = list.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));

4. Stream.takeWhile and Stream.dropWhile

Stream.takeWhile takes elements from the stream as long as the provided predicate is true, whereas Stream.dropWhile drops elements as long as the predicate is true and then takes the rest.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.stream()
.takeWhile(n -> n < 5)
.forEach(System.out::println); // Prints 1, 2, 3, 4

numbers.stream()
.dropWhile(n -> n < 5)
.forEach(System.out::println); // Prints 5, 6, 7, 8, 9

5. Collectors.teeing

Collectors.teeing allows us to perform two different collecting operations and then merge their results using a BiFunction.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int result = numbers.stream()
.collect(Collectors.teeing(
Collectors.summingInt(i -> i),
Collectors.counting(),
(sum, count) -> sum / count // Calculating the average
));
System.out.println(result); // Prints 3 (average of 1, 2, 3, 4, 5)

6. Stream.concat

Stream.concat is used to concatenate two streams into one.

Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<String> stream2 = Stream.of("d", "e", "f");
Stream<String> concatenatedStream = Stream.concat(stream1, stream2);
concatenatedStream.forEach(System.out::println); // Prints a, b, c, d, e, f

7. Collectors.partitioningBy

Collectors.partitioningBy partitions the elements of the stream into two groups based on a predicate. The result is a Map<Boolean, List<T>>.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(partitioned); // Prints {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8]}

8. IntStream for Ranges

IntStream provides a range of integers, making it easier to generate a sequence of numbers.

IntStream.range(1, 10)
.forEach(System.out::println); // Prints 1, 2, 3, 4, 5, 6, 7, 8, 9

IntStream.rangeClosed(1, 10)
.forEach(System.out::println); // Prints 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

These advanced features of the Java Stream API provide powerful tools for processing collections in a functional and declarative style. By mastering these concepts, you can write more efficient, readable, and maintainable Java code.

--

--

No responses yet