Java 8 streams

Streams API is a new addition in Java 8. Earlier we use the InputStream or OutputStream to process the streams while handling files and all. The new Streams API provides a set of classes to manipulate the elements in a collection. But, do not confuse with IO streams with Java 8 streams; the former deals with the bytes while the latter deals with the collection of objects. A stream is not a stored one rather it is a computed one. We can perform SQL like operations on this stream and get the desired result.

article

Streams API has following benefits

  • It provides a set of aggregate functions that simplifies coding. Ex: SQL like functions filter, map, reduce, find, match, and sorted are available for streams which helps to reduce a lot of code.

  • Has parallel processing capability which helps us to leverage the multi-core architecture of the server. This means that we do not need to type multi-threaded code explicitly, the stream will take care of multi-threading.

    Let us see how streams can reduce the amount of code while getting the all unique Usernames from the IT department:

    Pre Java 8 we do like:

    List<String> userNames=new ArrayList<String>(); for(Useruser :userList){ if("IT".equals(user.getDepartment())){ if(!userNames.contains(user.getName())){ userNames.add(user.getName()); } } }

    But with Streams we can

    List<String> userNames = userList.stream() .filter(user->"IT".equals(user.getDepartment())) .map(User::getName) .distinct() .collect(Collectors.toList());

    Clean code, right? More readability and less code are other advantages of streams.

    Here, we get a stream from the list of User objects, then operations like filter(), map(), distinct() are applied on that stream. We will see in detail what these operations mean. Finally, the resultant usernames collected in a List and we will return this list. Here we can find the use of lambda expressions [user -> "IT".equals(user.getDepartment())] and method references [User::getName].

    Stream Processing

    A stream will iterate the elements internally. We can attach listeners to the stream to process each element. These listeners are the call for each element. Audiences are called once for each element in the stream. It refers to as stream processing. The listeners of a stream will form a chain. Each listener in the chain process the element in the stream and then return a new element for the next listener to process.

    Terminal and Non-Terminal Operations

    The Stream listeners can perform a terminal or a non-terminal operation. A non-terminal stream operation is an operation that adds a listener to the stream which may modify the stream element. While a terminal stream operation, I return a result after stream processing. In the above example map() is a non-terminal operation and collect() is a terminal operation.

    Non-Terminal Operations

    When we add a non-terminal operation to the stream, it will produce a stream itself. Usually, these operations take a Java Lambda Expression as a parameter. Below are some examples.

  • filter()

    The filter() takes a lambda expression the current element will be included in the resulting stream if it satisfies the lambda expression.

    Here is an example of calling the Java Stream filter() method, will return all users of 'IT' department.

    Stream<User> longStringsStream = userList.stream() .map(User::getName);
  • map()

    map() method modifies the current element for instance if we need to convert all words in upper case.

    Stream<String> wordStream= wordList.stream() .map(String::toUpperCase);
  • distinct()

    This operation returns only the unique elements in a stream. Below returns the unique usernames.

    Stream<String> userNames = userList.stream() .map(User::getName) .distinct();

Terminal Operations

Terminal operations will return a single value. Below are some examples.

  • anyMatch()

    The anyMatch() method takes a lambda expression as a parameter and applies it to each element in the stream. If it evaluates to true for any element then, this operation will return a true value.

    boolean result= stream.anyMatch((value) -> { return value.startsWith("A"); }); System.out.println(result);
  • collect()

    This operation will collect the elements to a collection. In the above example, we can see this operation collects usernames in a list.

  • count()

    This operation will count the elements in a stream.

    long count = userList.stream() .map(User::getName) .count();
  • min() and max()

    As the name suggests these operations, will return the minimum and maximum elements.

  • toArray()

    This operation will return an array out of the stream elements.

Conclusion:

In this tutorial, we have seen the stream API in Java. The streams API provides many methods for processing the collection of objects. This help to reduce the amount of code and lambda expressions can use along with the streams. Thus, streams provide a functional approach for processing the collection elements. Java Developers India can use the built-in functionalities in this API for processing the collections.