Home  >  Blog  >   Java  > 

Java Stream Tutorial

In Java 8, a new abstract layer called Stream was introduced. You can use streams to process data declaratively, much like SQL statements. In this tutorial, you will learn to implement the operations and execute the features of java 8.

Rating: 4.8
  
 
671
  1. Share:

A stream is an abstraction of a set of non-mutable functions that have been applied to data in a particular order. There are no elements that can be stored in a stream. The fact that a stream lacks data storage is the key distinction between it and a structure. You cannot, for instance, point to a place in the stream where a specific element is present. Only the functions that use that data can be specified. Additionally, altering a stream will have an impact on the original stream.

Java 8 Streams API, which offers a mechanism for processing a set of data in various ways, including filtering and transformation, as well as any other methods that may be advantageous to an application, was introduced as a significant change. The Java 8 Streams API supports a different kind of iteration in which the set of items to be processed, the operation(s) to be carried out on each item, and the location in which the results of those operations are to be stored are all specified.

Table of Content - Java Stream Tutorial

What is Java 8?

The platform for developing Java programs has a new version called Java 8. It has a vast array of model upgrades (new Classes, JVM Java language, and libraries). Java 8 has features that improve productivity, usability, security, performance, and java programming technique. 

Java 8 additionally contains:

  • Lambda expressions
  • Method references
  • Default Methods (Defender methods)
  • A new Stream API.
  • Optional
  • A new Date/Time API.
  • Nashorn, the new JavaScript engine
  • Removal of the Permanent Generation
If you want to enrich your career and become a professional in Core Java, then visit Mindmajix - a global online training platform: "Core Java Online Course". This course will help you to achieve excellence in this domain.

Advantages of Java 8 Stream

Using streams in Java has many advantages, including the ability to write functions at a higher level of abstraction, which can reduce code bugs, the ability to condense functions into fewer and more readable lines of code, and the simplicity they provide for parallelization. Although Java streams are fairly well known, not everyone is familiar with how to fully utilize them, including the specifics of performing intermediate operations, creating streams, and performing terminal operations.

1. It has an Intuitive code:

Using streams has the added benefit of making the code more understandable and requiring less thought to operate. You'll see that the streams-based code almost reads like a list of steps the calculation takes. You must consider the variable's declaration in the for-loop in the example without streams, why it is being incremented by four, and the fact that occasionally, in between increments, we check to see if the variable's square is divisible by 10 and if it is, we append the sum. As you work with more complex functions, it becomes harder to reason about the calculations as they begin to combine different functionalities rather than proceeding step by step.

2. It is a less error-prone code:

Last but not least, the stream version of this function will be significantly less error-prone because of its step-by-step nature. You can work with streams at a higher level of abstraction than you would with a typical for-loop, which is much safer because streams involve higher-order functions.

MindMajix Youtube Channel

Features of Java 8 Stream:

Java 8 stream accompanies a lot of new and important features when compared to the java 7 version. Some important features are:

  1. forEach() method in the Iterable interface
  2. default and static methods in Interfaces
  3. Functional Interfaces and Lambda Expressions
  4. Java Time API
  5. Collection API improvements
  6. Concurrency API improvements
  7. Java IO improvements

what is new in java 8

Application of Java 8 Stream:

Java 8 streams are a pipeline that allows data to flow while also allowing functions to be applied to the data. In this case, a pipeline consists of a stream source, one or more intermediate operations, and one or more terminal operations. As a result, streams can be applied in a wide range of functions that rely on data. We have the ability to specify a series of data operations in small steps thanks to the streams API. We do not specify any contingent processing code, we resist the urge to create lengthy, intricate functions, and we are unconcerned with the direction of the data flow. Actually, we only concern ourselves with one step of data processing at a time: we compose the functions, and the data passes through the functions on its own thanks to the strength of the streams framework.

Related Article: Exception Handling in Java

Different Operations on Java 8 Stream:

Intermediate options:

1. A map: a stream made up of the outcomes of applying the specified function to the stream's elements is returned by the map method.

list number

2. A filter: Using the Predicate passed as an argument, the filter method selects elements.

list names

3. sorted: The stream is sorted using the sorted method.

Sorted Method

Terminal Operations:

1. collect: The stream's intermediate operations are used to return the results using the collect method.

collected method

2. forEach: To iterate through each element of the stream, use the forEach method.

ForEach

3. reduce: A stream's elements are condensed to a single value using the reduce method. A BinaryOperator is a parameter for the reduce method.

List number = Arrays.asList(2,3,4,5);

int even = number.stream().filter(x->x%2==0).reduce(0,(ans,i)-> ans+i);

Here ans variable is assigned 0 as the initial value and i is added to it .

Statistics:  

When performing stream processing, all statistics are calculated thanks to the introduction of statistics collectors in Java 8.

Statistics

Method Types and Pipelines:

As we've been discussing, there are intermediate and terminal operations in Java streams.

Filter() and other intermediary operations return a new stream that can be used for additional processing. The stream is marked as consumed by terminal operations like forEach(), at which point it can no longer be used.

The components of a stream pipeline are a stream source, one or more intermediate operations, and a terminal operation. Here is an example stream pipeline where the count is the final operation, filter() is the intermediate operation, and empList is the source:

Method Types and Pipelines 

Some processes are referred to as short-circuiting processes. Computations on infinite streams can be finished in a finite amount of time thanks to short-circuiting operations:

short-circuiting processes

Here, we short-circuit operations limit() to limit to 5 elements from the infinite stream created by iterate and skip() to skip the first 3 elements ().

What is Lazy Evaluation?

Java streams' ability to enable significant optimizations via lazy evaluations is one of their most crucial features. Only when the terminal operation is started is the source data used for computation, and source elements are only used when necessary. Since all intermediate operations seem to be lazy, they are not carried out until a processing result is actually required. Take the findFirst() example from earlier as an illustration. How often is the map() operation used in this? 4 times, as there are 4 elements in the input array.

Lazy evaluation

One element at a time, the stream executes the map and two filters. It starts by carrying out all of the operations on ID 1. Since ID 1's salary is less than $100,000, processing moves on to the subsequent component. The stream analyses the terminal operation findFirst() and goes back to the result because Id 2 satisfies both of the filter predicates. On ids 3 and 4, no operations are carried out. It is possible to avoid looking through all the data when processing streams lazily. The importance of this behavior increases when the input stream is infinite rather than just very large.

Comparisons-Based Stream Operations

1. Sorted: Let's start with the sorted() operation, which uses the comparator that we supply to order the stream's elements.

2. Min and max: The functions min() and max(), as their names imply, return the stream's minimum and maximum elements, respectively, using a comparator. Since a result might or might not exist, they return an Optional (due to, say, filtering)

3. Distinct(): Without taking an argument, the function distinct() returns the unique elements in the stream, removing any duplicates. To determine whether two elements are equal or not, it applies the equals() method of the elements.

4.allMatch, anyMatch and noneMatch: All of these operations require a predicate and give back booleans. As soon as the solution is found, short-circuiting is used, and processing is terminated. 

allMatch() determines whether the predicate holds for each and every element in the stream. 

Any element in the stream is examined by anyMatch() to determine whether the predicate is true.

The function noneMatch determines whether or not any elements match the predicate.

Related Article: String Handling in Java

What are Specialized Operations

Specialized streams offer more operations than a standard stream, which is very useful when working with numbers.

Examples are sum(), average(), range() etc

What are Reduction Operations

By applying a combining operation repeatedly, a reduction operation (also known as fold) takes a series of input elements and manages to combine them into a single summary result. FindFirst(), min(), and max are a few reduction operations that we have already seen (). Let's examine how the reduce() operation works in general.

1. Reduce:

The most common form of reduce() is:

Reduce

where identity is the starting value and accumulator is the binary operation we repeatedly apply.

2. Joining:

The delimiter will be inserted between the two String elements of the stream by Collectors.joining(). Internally, the joining operation is handled by a java.util.StringJoiner.

3.toSet:

We can also use toSet() to get a set out of stream elements

4.toCollection:

By passing a SupplierCollection> to Collectors.toCollection(), we can extract the elements into any other collection. A function Object() reference can also be used for the Supplier.

5.Summarizingdouble:

Another intriguing collector is summarizingDouble(), which appears to apply a double-producing mapping function to every input element and returns a special class with statistical data for the values it produces.

6. Partitioningby:

Depending on whether or not the elements meet certain requirements, we can divide a stream into two parts.

7.Groupingby:

The advanced partitioning function groupingBy() allows us to divide the stream into more than two groups. As a parameter, it accepts a classification function. Every component of the stream is subjected to this classification function. The function's value is used as a key to the map we obtain from the grouping using the collector. 

8. Mapping:

groupingBy(), which was mentioned in the section above, uses a Map to group the stream's elements. But occasionally it might be necessary to classify data into a type other than the element type. Here's how we can accomplish that using the mapping() function, which can convert the collector to a different type:

9. Reducing:

Similar to reduce(), which we previously explored, is reducing(). It merely provides a collector that reduces the input elements.In a multi-level reduction, following groupingBy() or partitioningBy, reducing() is most helpful (). Use reduce() instead to perform a straightforward reduction on a stream.

10. A parallel stream:

Without writing any boilerplate code, we can perform stream operations in parallel using the support for parallel streams; all we need to do is mark the stream as parallel. Similar to writing multithreaded code, there are a few considerations when using parallel streams:

  • Making sure the code is thread-safe is a must. If the parallel operations modify shared data, extra caution must be used.
  • If the execution order of operations or the order in which data is returned in the output stream matters, we shouldn't use parallel streams. In the case of parallel streams, for instance, operations like findFirst() may produce a different result.
  • We should also make sure that running the code in parallel is worthwhile. Understanding the operation's performance characteristics in particular, as well as the system's overall performance, is obviously crucial in this situation.
Related Article: Java stream interview questions

11. An Infinite stream:

We may occasionally want to carry out operations even though the elements are still being generated. The number of elements we'll require might not be known in advance. We can use infinite streams, also referred to as unbounded streams, in contrast to using lists or maps where all the elements are already filled in. There are two methods for producing endless streams:

1. Generate: Every time fresh stream elements need to be generated, we provide a Supplier to generate() that is called. With infinite streams, we must offer a circumstance under which the processing will eventually come to an end. Utilizing the limit is a typical method for doing this ().

2. An Iterate: Iterate() requires two inputs: a seed element (an initial value) and a function that creates the subsequent element using the seed element. Since iterate() is stateful by design, it might not be useful for parallel streams.

Related Article: Control Statements in Java

What are File Operations

1. File write operation

Here, we call PrintWriter.println and use forEach() to write each element of the stream into the file ()

2. File read operations

Here, Files.lines() returns the file's lines as a Stream, which getPalindrome() uses for additional processing.getPalindrome() operates on the stream without knowing how it was created. Additionally, this makes unit testing easier and increases code reuse.

Conclusion

When it comes to all the ways to implement streams, intermediate operations, and terminal operations in Java, this article is merely scratching the surface. You have access to a lot more, plus you can make your own unique ones. You can write more elegant and error-free Java code with streams, and as you get better at using streams, you'll notice that some operations get much simpler to use!

Join our newsletter
inbox

Stay updated with our newsletter, packed with Tutorials, Interview Questions, How-to's, Tips & Tricks, Latest Trends & Updates, and more ➤ Straight to your inbox!

Course Schedule
NameDates
Core Java TrainingFeb 07 to Feb 22
Core Java TrainingFeb 11 to Feb 26
Core Java TrainingFeb 14 to Mar 01
Core Java TrainingFeb 18 to Mar 05
Last updated: 04 February 2023
About Author
Remy Sharp
Madhuri Yerukala

Madhuri is a Senior Content Creator at MindMajix. She has written about a range of different topics on various technologies, which include, Splunk, Tensorflow, Selenium, and CEH. She spends most of her time researching on technology, and startups. Connect with her via LinkedIn and Twitter .

Recommended Courses

1 /15