You are here
Home > java > Core Java >

Stream Java 9 Improvements

Stream Java 9 Improvements Java Stream API became an important feature with the release of Java 8, enabling developers to perform functional-style operations on collections with comfort. Subsequently, Java 9 introduced a new set of improvements that additionally improved the capabilities of streams, making them even more powerful and developer-friendly. In this article, we will explore the stream java 9 improvements, including complete code examples, highlighting their advantages in writing cleaner, shorter, and effective code.

Stream Java 9 Improvements

Java 9 has introduced a set of improvements in Stream API methods to enhance its capabilities and making it more developer-friendly. Some of the major improvements are:

1) takeWhile()

2) dropWhile()

3) iterate()

4) ofNullable()

Let’s go through them one by one in the subsequent sections.

takeWhile( )

The takeWhile() method is an enhancement to the Java Stream API, introduced in Java 9. It filters elements from a stream based on a defined condition until the condition becomes false. Once the condition becomes false for an element in the stream, the takeWhile() operation instantly stops processing further elements, resulting in a short-circuiting behavior.

The purpose of the takeWhile() method is to process ordered streams effectively, particularly when working with sorted data. It enables developers to avoid needless iterations through the entire collection by stopping stream processing as soon as the condition is no longer met.

Syntax

Stream<T> takeWhile(Predicate<? super T> predicate)

Parameters:

predicate: A predicate functional interface that represents the condition to be applied to each element in the stream. The method will keep taking elements from the stream as long as the predicate returns to true for the element.

Return Type:

The takeWhile() method returns a new Stream containing the elements from the original stream that satisfy the given condition until the first occurrence of an element for which the condition is false.

Example

public class TakeWhileExample {
   public static void main(String[] args) {
      List<Integer> numbers = Arrays.asList(1, 3, 5, 4, 2, 6, 7, 8);

      List<Integer> result = numbers.stream()
         .takeWhile(n -> n % 2 == 1) // Take elements until the condition (n % 2 == 1) is false.
         .toList(); 

      System.out.println(result);   // Output: [1, 3, 5]
   }
}

In the example above, we have a list of integers. We use the takeWhile() method to filter elements from the stream until the condition n % 2 == 1 (i.e., the number is odd) is false. As soon as the first even number (4) is met, the takeWhile() operation stops processing the stream further, resulting in the output [1, 3, 5].

It’s important to note that the takeWhile() method processes the elements in the order they appear in the stream. For unordered streams, the behavior of takeWhile() is not guaranteed, and using sorted() or unordered() operations before takeWhile() could lead to unexpected results.

Problem Scenario

Let’s consider a problem scenario of Online Shopping Cart Checkout. In this scenario, let’s consider an online shopping platform that processes orders from customers. As customers add items to their shopping cart and proceed to checkout, the system generates a stream of items representing their shopping cart. The platform provides a discount for certain items, but the discount is only applicable to the first few items meeting specific criteria. The system needs to identify and apply the discount only to those eligible items in the shopping cart.

In this scenario, the takeWhile() method would be the best choice to efficiently process the shopping cart items and apply the discount to eligible items until a certain condition is met.

Solution Using takeWhile( ) 

Let’s check how the takeWhile() method can be used in this scenario:

import java.util.stream.Stream;
import java.util.List;
import java.util.ArrayList;

public class OnlineShoppingCartCheckout {
   public static void main(String[] args) {
      List<Item> shoppingCart = new ArrayList<>();
       // Simulate a stream of items in the shopping cart as customers add products

      Stream<Item> cartItemStream = shoppingCart.stream();

      List<Item> itemsWithDiscount = cartItemStream
         .takeWhile(item -> item.getPrice() >= 50) // Add items until the price>=50
         .toList(); 

      double totalDiscount = 0.0;
      double totalPrice = 0.0;

        // Apply discount only to the eligible items
      for (Item item : itemsWithDiscount) {
        double discount = item.getPrice() * 0.1; // Assuming a 10% discount
        totalDiscount += discount;
        totalPrice += item.getPrice() - discount;
      }

      System.out.println("Discount applied: $" + totalDiscount);
      System.out.println("Total Price (after discount): $" + totalPrice);
   }
}

class Item {
   private String name;
   private double price;

   public Item(String name, double price) {
      this.name = name;
      this.price = price;
   }  

   public String getName() {
      return name;
   }

   public double getPrice() {
      return price;
   }
}

In this example, we have a list of Item objects representing the shopping cart. The cartItemStream is generated from this list, simulating the stream of items in the shopping cart as customers add products. We then use the takeWhile() method to process the stream of items until a non-discounted item (with a price below $50) is found. As soon as a non-discounted item is found, the takeWhile() operation will stop processing the stream, ensuring that only eligible items with prices above or equal to $50 are considered for the discount.

In this way, using takeWhile() in this scenario is beneficial because it efficiently identifies and applies the discount only to the eligible items at the beginning of the shopping cart. It will also prevent unnecessary discount calculations for the remaining items.

dropWhile( )

The dropWhile() method is another useful addition, under the improvements of Stream API in Java 9. It acts just opposite to the takeWhile() method. It skips elements from the beginning of a stream based on a specified condition until the condition becomes false. Once the condition becomes false for an element in the stream, the dropWhile() operation stops skipping elements and includes all remaining elements in the output stream.

The main purpose of the dropWhile() method is to discard elements from the start of the stream that do not meet a specific criteria. It enables developers to exclude initial data that does not satisfy the given condition, thus concentrating on relevant data for further processing.

Syntax

Stream<T> dropWhile(Predicate<? super T> predicate)

Parameters

predicate: A functional interface that represents the condition to be applied to each element in the stream. The method will keep skipping elements from the stream as long as the predicate evaluates to true for the element.

Return Type

The dropWhile() method returns a new Stream containing the elements from the original stream after skipping elements until the first occurrence of an element for which the condition is false.

Example

public class DropWhileExample {
   public static void main(String[] args) {
      List<Integer> numbers = Arrays.asList(1, 3, 5, 2, 4, 6, 7, 8);

      List<Integer> result = numbers.stream()
        .dropWhile(n -> n % 2 == 1) // Skip elements until the condition (n % 2 == 1) is false.
        .toList(); 

      System.out.println(result); // Output: [2, 4, 6, 7, 8]
   }
}

In the example above, we have a list of integers named numbers. We use the dropWhile() method to skip elements from the stream until the condition n % 2 == 1 (i.e., the number is odd) becomes false. As soon as the first even number (2) is encountered, the dropWhile() operation stops skipping elements, and all remaining elements are included in the output stream, resulting in the output [2, 4, 6, 7, 8].

takeWhile( ) and dropWhile( ) Custom methods Using Java 8

If you are using JDK 8 environment, you can still obtain the functionality of takeWhile() and dropWhile() methods, but you have to write the custom methods as below. These custom methods will also tell you that you are saving a multiple number of lines when using inbuilt methods introduced in Java 9.

public static <T> List<T> takeWhile(List<T> list, Predicate<T> predicate) {
   List<T> result = new ArrayList<>();
   for (T item : list) {  
     if (predicate.test(item)) {
      result.add(item);
     } else {
      break;
     }
   }
   return result;
}
public static <T> List<T> dropWhile(List<T> list, Predicate<T> predicate) {
   List<T> result = new ArrayList<>();
   boolean foundFirstNonMatchingElement = false;

   for (T item : list) {
     if (!foundFirstNonMatchingElement && predicate.test(item)) {
     continue; // Skip elements that match the predicate until the first non-matching element is found.
   }

   foundFirstNonMatchingElement = true;
   result.add(item);
}

return result;
}

iterate( )

The iterate() method in the Stream API was introduced in Java 9 as an enhancement to the existing iterate() method in Stream API Java 8. Both methods allow to create streams of sequential elements based on a specified function, but the Java 9 version provides additional functionality that improves its effectiveness and usefulness.

In order to have a clear understanding of enhancement in Java 9, let’s first understand the iterate() method introduced in Java 8.

Java 8 iterate() method

In Java 8, the iterate() method was part of the Stream interface and had the following signature:

Syntax

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)

Parameters

seed: The initial value of the stream, from which the iteration begins.
f: A unary operator that defines the function to generate the next element of the stream based on the previous element.

Return Type

The iterate() method returns an infinite sequential ordered Stream, which starts with the provided initial value (seed) and applies the specified function f to generate next elements.

Example (Java 8)

public class IterateExampleJava8 {
   public static void main(String[] args) {
      Stream.iterate(1, n -> n * 2)
            .limit(5)
            .forEach(System.out::println);
   }
}

Output (Java 8)

1
2
4
8
16

In the Java 8 example above, the iterate() method creates an infinite stream starting with the initial value 1. The second parameter n -> n * 2 defines the function to generate the next element by doubling the previous one. The iteration continues indefinitely. To limit the number of elements in the stream, we use the limit() method to stop the iteration after the first 5 elements. This way, we get the output [1, 2, 4, 8, 16], as the iteration stops after generating the fifth element. The iteration continues until the stopping condition becomes false.

Java 9 iterate() method

The Java 9 iterate() method builds upon the functionality of the Java 8 version by introducing a more convenient and streamlined way to handle iterations with a stopping condition. It was enhanced to include a new overload with an additional parameter for specifying a predicate in order to stop the iteration.

Syntax

static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

Parameters

seed: The initial value of the stream, from which the iteration begins.
hasNext: A predicate that defines the condition to stop the iteration. The iteration continues as long as this predicate evaluates to true.
next: A unary operator that generates the next element of the stream based on the previous element.

Return Type

The Java 9 iterate() method also returns an infinite sequential ordered Stream, but it stops when the specified hasNext predicate evaluates to false.

Example (Java 9)

public class IterateExampleJava9 {
   public static void main(String[] args) {
       Predicate<Integer> hasNext = n -> n < 100;
       Stream.iterate(1, hasNext, n -> n * 2)
             .forEach(System.out::println);
   }
}

Output (Java 9)

1
2
4
8
16
32
64

In the Java 9 example above, we use the enhanced iterate() method with the new predicate parameter hasNext. The iteration continues as long as the predicate n -> n < 100 evaluates to true, meaning the next element n is less than 100. Once the predicate evaluates to false (when n becomes 128), the iteration stops.

The primary difference between the Java 8 and Java 9 iterate() methods is the additional predicate parameter in the Java 9 version. This enhancement provides a more efficient and expressive way to stop the iteration without relying on external logic or intermediate stream operations like limit(), which was the case in Java 8.

OfNullable( )

Java 9 introduced the Stream.ofNullable() method, allowing null elements to be included directly in a stream. This avoids the need for explicit null checks. It provides a convenient way to create a stream from a single element, including null elements. This method simplifies the process of handling nullable values within streams, avoiding the need for explicit null checks. It offers developers to work with null elements more efficiently.

Syntax

static <T> Stream<T> ofNullable(T t)

Parameter

t: The element to be included in the stream, which can be null.

Return Type

The ofNullable() method returns a sequential stream containing either a single non-null element, if the provided element is not null, or an empty stream if the provided element is null.

Example (Java 9)

public class OfNullableExample {
   public static void main(String[] args) {
       String name = "James";
       String nullName = null;

       Stream<String> nameStream = Stream.ofNullable(name);
       Stream<String> nullNameStream = Stream.ofNullable(nullName);

       System.out.println(nameStream.count()); // Output: 1 (one element)
       System.out.println(nullNameStream.count()); // Output: 0 (No element, stream is empty)
   }
}

In the example above, we have two String variables name and nullName. We use the ofNullable() method to create two separate streams, one for each variable. The first stream nameStream contains the non-null element “John”, while the second stream nullNameStream is empty as the provided element is null. This illustrates how the ofNullable() method includes non-null elements in the stream and returns an empty stream for null elements.

In Java 8, we need to use conditional statements or intermediate operations like filter() to handle null elements.

Example (Customized Using Java 8)

Let’s observe the example below by using the ternary conditional operator [? :] to achieve the same functionality.

public class OfNullableExampleJava8 {
   public static void main(String[] args) {
       String name = "James";
       String nullName = null;

       Stream<String> nameStream = (name != null) ? Stream.of(name) : Stream.empty();
       Stream<String> nullNameStream = (nullName != null) ? Stream.of(nullName) : Stream.empty();

       System.out.println(nameStream.count()); // Output: 1 (one element)
       System.out.println(nullNameStream.count()); // Output: 0 (No element, stream is empty)
   }
}

In the Java 8 example above, we can achieve similar functionality to ofNullable() by using ternary conditional operators [? :] to check if the element is null. If the element is not null, we create a stream using Stream.of(), and if it is null, we create an empty stream using Stream.empty(). This approach requires additional code and makes the stream creation process less straightforward compared to the easiness provided by ofNullable() in Java 9.

In conclusion, the ofNullable() method introduced in Java 9 simplifies the creation of streams with nullable elements, eliminating the need for explicit null checks. It provides a more concise and expressive way to handle null elements in streams, enhancing code readability and reducing the chances of null-related errors.

Conclusion

Using these functionalities offered by Java 9’s Stream enhancements, developers can deal with complex data processing tasks with comfort and create more maintainable and efficient code. These enhancements reflect Java’s ongoing commitment to improving its language features and APIs. It enables developers to build better software solutions in a more concise and expressive manner.

FAQ

What is the difference between iterate() method of Stream API in Java 8 vs. Java9?

In Java 8, the iterate() method does not directly take a predicate to stop the iteration like in Java 9. Instead, we use the limit() method or other intermediate operations to control the size of the generated stream or apply additional filters to carry out similar behavior.

Leave a Reply


Top