Java Streams offers a robust and declarative approach to processing collections of data in Java, initially introduced in Java 8. With Java 9, several enhancements to the Streams API make it even more powerful and expressive. In this section, we’ll explore key improvements, including new methods like takeWhile, dropWhile, iterate, ofNullable, and concat.

takeWhile

The takeWhile method is a significant addition to the Streams API in Java 9. It allows you to consume elements from a stream as long as a specified condition holds. Once the condition becomes false, the method stops and returns a new stream containing only the elements that match the predicate.

View this as a conditional filter with a short-circuiting capability. Here’s a simple example to illustrate:

Stream.iterate(1, i -> i + 1)
                .takeWhile(n -> n <= 10)
                .map(x -> x * x)
                .forEach(System.out::println);

In the example above, we create an infinite stream starting from 1, then use takeWhile to select numbers less than or equal to 10, calculate their squares, and print them.

You might wonder how takeWhile differs from the filter method. Consider the following:

Stream.iterate(1, i -> i + 1)
      .filter(x -> x <= 10)
      .map(x -> x * x)
      .forEach(System.out::println);

While both examples yield the same result in this scenario, the difference lies in how takeWhile operates. It stops processing as soon as the predicate is false, whereas filter evaluates the entire stream:

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
      .takeWhile(x -> x <= 5)
      .forEach(System.out::println);

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
      .filter(x -> x <= 5)
      .forEach(System.out::println);

The output for takeWhile will be:

1
2
3
4
5

While the output for filter will be:

1
2
3
4
5
0
5
4
3
2
1
0

As demonstrated, takeWhile stops once it encounters the first non-matching element, whereas filter continues through the entire stream.

dropWhile

The dropWhile method, introduced in Java 9, acts as the complement to takeWhile. Instead of taking elements while a condition is true, dropWhile skips elements while the condition is true and starts returning elements once the condition becomes false.

Consider the following example:

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
      .dropWhile(x -> x <= 5)
      .forEach(System.out::println);

The output will be:

6
7
8
9
0
9
8
7
6
5
4
3
2
1
0

In this example, dropWhile skips all elements less than or equal to 5 and starts including elements once it encounters the first element greater than 5.

Enhanced iterate Method

Java 8 introduced the iterate method, which allowed the creation of infinite streams using a seed and a unary operator. However, the method lacked a way to terminate the loop, making it suitable primarily for infinite streams. Java 9 addresses this limitation by introducing an overloaded version of iterate with an additional predicate parameter to control termination:

Stream.iterate(1, i -> i < 256, i -> i * 2)
      .forEach(System.out::println);

The above code generates a stream of powers of two, stopping when the value reaches or exceeds 256. This new iterate method behaves like a for loop and performs as a modern alternative:

for (int i = 1; i < 256; i *= 2) {
      System.out.println(i);

}

ofNullable

The ofNullable method, another valuable addition in Java 9, provides a convenient way to create a stream from a single nullable element. This method avoids potential NullPointerExceptions and helps keep your code cleaner:

Integer number = null;
Stream<Integer> result = Stream.ofNullable(number);
result.map(x -> x * x).forEach(System.out::println);

If the number is nullofNullable returns an empty stream, effectively preventing runtime errors in cases where a null value would usually cause issues.

This approach simplifies scenarios where a value might be null, offering a more concise way to handle optional values:

Stream<Integer> result = number != null
    ? Stream.of(number)
    : Stream.empty();

concat

The contact method in Java Streams is another useful feature for combining streams. It allows you to concatenate two or more streams into a single stream, which is particularly handy when you need to merge data from multiple sources into a unified stream for further processing. The contact method merges two streams into a single stream.

This method can be used with streams of any type (Stream<T>, IntStream, LongStream, or DoubleStream). When applied, it creates a new stream that contains all elements from the first stream followed by all elements from the second stream.

Stream.concat(Stream<? super T> a, Stream<? extends T> b)

Combines two streams of the same type.

Stream<String> firstStream = Stream.of("A", "B", "C");
Stream<String> secondStream = Stream.of("D", "E", "F");

Stream<String> concatenatedStream = Stream.concat(firstStream, secondStream);
concatenatedStream.forEach(System.out::println);

In this example, the output will be:

A
B
C
D
E
F

The concat method merges firstStream and secondStream, resulting in a stream where all elements from firstStream are followed by all elements from secondStream.

Special Cases for Primitive Streams

The contact method also works with specialized streams like IntStream, LongStream, and DoubleStream. Here’s how it can be used with these primitive types:

IntStream.concat(IntStream a, IntStream b)

Concatenates two IntStream instances.

IntStream firstStream = IntStream.of(1, 2, 3);
IntStream secondStream = IntStream.of(4, 5, 6);

IntStream concatenatedStream = IntStream.concat(firstStream, secondStream);
concatenatedStream.forEach(System.out::println);

This will output:

1
2
3
4
5
6

LongStream.concat(LongStream a, LongStream b)

Concatenates two LongStream instances.

LongStream firstStream = LongStream.of(10L, 20L, 30L);
LongStream secondStream = LongStream.of(40L, 50L, 60L);

LongStream concatenatedStream = LongStream.concat(firstStream, secondStream);
concatenatedStream.forEach(System.out::println);

This will output:

10
20
30
40
50
60

DoubleStream.concat(DoubleStream a, DoubleStream b)

Concatenates two DoubleStream instances.

DoubleStream firstStream = DoubleStream.of(1.1, 2.2, 3.3);
DoubleStream secondStream = DoubleStream.of(4.4, 5.5, 6.6);

DoubleStream concatenatedStream = DoubleStream.concat(firstStream, secondStream);
concatenatedStream.forEach(System.out::println);

This will output:

1.1
2.2
3.3
4.4
5.5
6.6

The enhancements brought to the Streams API in Java 9, including takeWhiledropWhileiterateofNullable, and concat, provide developers with more control and flexibility in stream processing.

Post a Comment

Previous Post Next Post