In Java, a Stream is a series of data from various sources, including lists, arrays, sets, and other collections. Unlike collections, which must gather all the values before beginning processing, streams can do so asynchronously. The stream can be thought of as a pipeline in which data is processed as it is needed. This article compiles all of the blog posts covering the basics of the Jave Stream API and associated topics.
We all have heard the term java. It is a programming language or, in other terms, an object-oriented programming language and software platform. It is used in billions of devices, consoles, games, and many more. Learning Java is helpful in this century; as we discussed, many software use java as a programming language and even for designing games we all love and enjoy playing.
One of Java 8's most notable additions was support for streams. This in-depth Java stream tutorial serves as an introduction, and it will cover a wide range of features made possible by streams, emphasizing concrete, real-world applications. Data is never stored in a stream, so it cannot be considered a data structure. And it never changes the original data sources, either.
Java Stream Tutorial - Table of Contents |
Java streams enable us with functional-style operations on streams of elements. A stream is just an abstraction of a non-mutable collection of functions applied in some order to the data. A stream is not a collection where you can easily store elements.
Functionality is managed via a Java component. It is made feasible by java.util.stream, which allows for functional-style operations on stream items that are analogous to the map-reduce transformations performed on collections. The descriptions will be easier to grasp because of the visuals.
Now, let's look at a few easy and simple examples of how to create and use streams before we get into terms and core ideas.
An example is displayed here.
private static Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100.0),
new Employee(2, "Bill Gates", 200.0),
new Employee(3, "Mark Zuckerberg," 300.0)
};
Stream.of(arrayOfEmps);
Note: In Java 8, the Collection interface now contains a new stream() function.
We may also create a stream for any individual object using stream.of():
Or we can use stream.builder():
Example:
Stream.Builder<Employee> empStreamBuilder = Stream.builder();
empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);
Stream<Employee> empStream = empStreamBuilder.build();
Want to acquire industry skills and gain complete knowledge of Java? Enroll in Instructor-Led live Core Java Training to become Job Ready! |
Let's look at some popular applications of the new stream language and specific operations we may execute on it and with its help.
forEach
It is one of the most simple and joint operations; it loops over stream elements. The method is so standard that it is introduced directly in Iterable, Map, and many more.
Example:
@Test
public void whenIncrementSalaryForEachEmployee_thenApplyNewSalary()
{
empList.stream().forEach(e -> e.salaryIncrement(10.0));
assertThat(empList, contains(
hasProperty("salary", equalTo(110000.0)),
hasProperty("salary", equalTo(220000.0)),
hasProperty("salary", equalTo(330000.0))
));
}
If you use the forEach() function, the stream pipeline will be considered consumed and unusable once the procedure has finished.
Related article: Exception Handling in java |
map
A new stream is created by the map() function when a function is applied to each element of the previous stream, also known as the "original stream." The new stream could branch off into a few different directions.
The following code snippet shows how to transform a sequence of Integers into an equal sequence of Employees.
@Test
public void whenMapIdToEmployees_thenGetEmployeeStream()
{
Integer[] empIds = { 1, 2, 3 };
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.collect(Collectors.toList());
assertEquals(employees.size(), empIds.length);
}
In this section, we convert an array of employee IDs into an integer stream. Because each integer is given to the function employeeRepository::findById(), which returns the object representing the relevant employee, an employee stream is essentially created.
collect
We got a glimpse of how the collection works in the example mentioned earlier; it is one of the most common ways to get stuff or works out of the stream when we have done or completed the processing:
@Test
public void whenCollectStreamToList_thenGetList()
{
List<Employee> employees = empList.stream().collect(Collectors.toList());
assertEquals(empList, employees);
}
The collect() method applies mutable fold operations on the data items kept in the Stream instance. These processes include repackaging elements to various data structures and applying further logic, concatenating them, and so on.
The implementation of the Collector interface supplies the strategy that should use for this operation. In the preceding illustration, we gathered all of the Stream items into a List instance with the help of the toList collector.
Related article: Interface in Java |
filter
filter() produces a new stream that consists of a component of the original stream which passes a given test
Example:
@Test
public void whenFilterEmployees_thenGetFilteredStream()
{
Integer[] empIds = { 1, 2, 3, 4,5 };
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 200000)
.collect(Collectors.toList());
assertEquals(Arrays.asList(arrayOfEmps[2]), employees);
}
In the above example, null references for invalid employee ids are filtered away before a second filter is used to retain only workers with wages over a specified threshold.
findFirst
It returns an optional entry in the stream, and the options can be empty.
Example:
@Test
public void whenFindFirst_thenGetFirstEmployeeInStream()
{
Integer[] empIds = { 1, 2, 3, 4,5 };
Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);
assertEquals(employee.getSalary(), new Double(200000));
}
This section returns the first employee whose annual compensation is higher than 100,000 dollars. If there is no such employee, it will return the value null.
Checkout: [Polymorphism in Java]
toArray
We learned how to collect() works to get out of the stream. If there is a need to get out of the stream, we just need to use Array():
Example:
@Test
public void whenStreamToArray_thenGetArray()
{
Employee[] employees = empList.stream().toArray(Employee[]::new);
assertThat(empList.toArray(), equalTo(employees));
}
flatMap
flatMap holds a bit complex data structures like stream <List<String>>
Example
@Test
public void whenFlatMapEmployeeNames_thenGetNameStream()
{
List<List<String>> namesNested = Arrays.asList(
Arrays.asList("Jeff", "Bezos"),
Arrays.asList("Bill", "Gates"),
Arrays.asList("Mark", "Zuckerberg"));
List<String> namesFlatStream = namesNested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
assertEquals(namesFlatStream.size(), namesNested.size() * 2);
}
Note: notice how we were able to change or convert Stream<List<String>> to a more straightforward Stream<String> – using the flatMap() API.
peek
ForEach(), a terminal operation, was demonstrated previously in this section. Before applying any terminal action, we might need to run many operations on each stream element.
Also read: Inheritance in Java |
Peek ()
In such circumstances, the peek() function could come in handy. To put it more simply, it does the action provided on each stream element and then returns a new stream that may use in subsequent processing. Peek () is an operation that occurs in between:
Example:
@Test
public void whenIncrementSalaryUsingPeek_thenApplyNewSalary()
{
Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg," 300000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream()
.peek(e -> e.salaryIncrement(10.0))
.peek(System.out::println)
.collect(Collectors.toList());
assertThat(empList, contains(
hasProperty("salary", equalTo(110000.0)),
hasProperty("salary", equalTo(220000.0)),
hasProperty("salary", equalTo(330000.0))
));
}
filter() and other similar intermediate procedures create a new stream that may use for further processing. When a stream is consumed by a terminal operation like forEach(), it can no longer use it.
In a stream pipeline, the first component is the stream source, then there may be one or more actions in between, and finally, there will be an end operation.
Here's a sample stream pipeline where emptiest is the source, filter() is the intermediate operation, and count over here is the terminal operation:
@Test
public void whenStreamCount_thenGetElementCount()
{
Long empCount = empList.stream()
.filter(e -> e.getSalary() > 200000)
.count();
assertEquals(empCount, new Long(1));
}
Some of the processes can be categorized as "short-circuiting." Employing "short-circuiting" methods makes it possible to perform calculations on infinite streams in a finite amount of time.
Example:
@Test
public void whenLimitInfiniteStream_thenGetFiniteElements() {
Stream<Integer> infiniteStream = .Stream.iterate(2, i -> i * 2).;
List<Integer> collect = infiniteStream
.skip(3)
.limit(5)
.collect(Collectors.toList());
assertEquals(collect, Arrays.asList(16, 32, 64, 128, 256));
}
In the above example, we used short-circuiting operations skip() to skip the first three elements and limit() to limit to 5 elements from the infinite stream generated using iterate().
Lazy evaluation is one of the most critical parts of streams. As such, it is also one of the essential features of streams since it makes considerable improvements possible. As a result, lazy evaluation is one of the most important qualities of streams.
When the terminal action is started, the computation on the source data is carried out, and the source items are only consumed when it is essential to accomplish the task at hand. All activities that occur over the course of processing are referred to as being lazy, which means that they are placed on hold until it is essential to generate the result. It holds true for all the actions that occur during the processing.
Example:
@Test
public void whenFindFirst_thenGetFirstEmployeeInStream()
{
Integer[] empIds = { 1, 2, 3, 4,5 };
Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);
assertEquals(employee.getSalary(), new Double(200000));
}
Related Article: Java Annotations |
We got the basics of the java stream, and the details mentioned earlier will help you in developing knowledge for the java stream. We already discussed that Java is the essential programming language, and it is used in software and has a bright future. Learning the language in depth will surely help excellent or high-knowledge programmers.
It may seem complex or challenging to first-time users, but it will be easy with time and proper practice, as we know practice makes things easier. Making notes might help better learning instead of just reading it from the web page and trying to understand it. The examples mentioned are for better understanding any problem in understanding the theory portion; the examples might help in understanding the function of the element.
You liked the article?
Like: 1
Vote for difficulty
Current difficulty (Avg): Medium
TekSlate is the best online training provider in delivering world-class IT skills to individuals and corporates from all parts of the globe. We are proven experts in accumulating every need of an IT skills upgrade aspirant and have delivered excellent services. We aim to bring you all the essentials to learn and master new technologies in the market with our articles, blogs, and videos. Build your career success with us, enhancing most in-demand skills in the market.