1. Preface
Stream is a very important new feature in Java8. From this article, I will introduce each of the features Stream provides and show you how to use Stream with a simple and practical example.
2. Introduction
Note that first of all, we should not confuse Java 8 Stream with Java I/O, they have little to do with each other.
Simply put, Stream is a wrapper for data sources that allows us to manipulate them and batch them quickly and easily.In addition, Stream does not store data, it is not a data structure, and it does not modify the data source.
Java.util.streamFunctional-level operations are supported on each of these elements, such as map-reduce transformations on collect.
Before we get to know the terms and concepts, let's take a few simple examples to show you how to create a Stream and how to use it.
2.1 Create Stream
Let's start by creating Stream with Array
private static Employee[] arrayOfEmps = { new Employee(1, "Jeff Bezos", 100000.0), new Employee(2, "Bill Gates", 200000.0), new Employee(3, "Mark Zuckerberg", 300000.0) }; Stream.of(arrayOfEmps);
We can also generate Stream from List
private static List<Employee> empList = Arrays.asList(arrayOfEmps); empList.stream();
In Java8, we can use Stream.of() Create Stream from a single object
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);
You can also use simple Stream.builder() to create:
Stream.Builder<Employee> empStreamBuilder = Stream.builder(); empStreamBuilder.accept(arrayOfEmps[0]); empStreamBuilder.accept(arrayOfEmps[1]); empStreamBuilder.accept(arrayOfEmps[2]); Stream<Employee> empStream = empStreamBuilder.build();
Of course, there are other ways to create Stream, as you will see below
3. Stream operations
forEach
forEach is the simplest and most common operation that traverses each element of the Stream and executes the corresponding supplied[^supply] function.
The forEach method is widely used in many data structures, such as Iterable,Map, and so on.
@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)) )); }
ForEach is a final operation, which means that after forEach execution, the Stream pipeline can no longer be treated as a Stream and should be consumed.In the next section we will talk about what terminal operations are.
map
Executing a function on the original stream with map() results in a new stream that can be of a different type than the original stream.
The following example shows you how to convert an Integer-type stream to an Employee-type stream.
@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); }
Here, Mr. A makes an integer array, and each integer is passed to the employeeRepository::findById method, which returns the corresponding Employee object and generates the Employee stream.
collect
Once the stream operation processing is complete, we can convert Stream to a collection using the collect method.
@Test public void whenCollectStreamToList_thenGetList() { List<Employee> employees = empList.stream().collect(Collectors.toList()); assertEquals(empList, employees); }
The collect repackages the elements in Stream and performs the appropriate operations to convert them into their corresponding data structures in Java.
This strategy is implemented through the Collector interface, which converts elements in Stream into List instances through toList.
filter
Next, let's take a look at filter(), which executes a filter and produces a new Stream that contains only the elements from the original Stream that meet the criteria.
Let's see how he works:
@Test public void whenFilterEmployees_thenGetFilteredStream() { Integer[] empIds = { 1, 2, 3, 4 }; 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 example above, invalid Employees referenced as null are first filtered out, leaving only Employees with a salary greater than 200,000.
findFirst
findFirst returns the first element in the stream and wraps it in Optional, although the returned Optional may be empty.
@Test public void whenFindFirst_thenGetFirstEmployeeInStream() { Integer[] empIds = { 1, 2, 3, 4 }; 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)); }
Here, you can see that the first employee with a salary greater than 100,000 will be returned, and if not null.
summary
This article explains how to create a Stream and the important operations in Stream, such as forEach, map, collect, filter, findFirst.
In the next chapter we will talk about Stream concepts, such as what is an intermediate operation and what is a final operation.And tell you how to make our code look simpler through streaming.