What are some examples of USES of the new Java8 feature lambda expressions

  • 2020-04-01 03:22:20
  • OfStack

We've been waiting a long time for lambda to bring the concept of closures to Java, but we lose a lot of value if we don't use it in collections. The problem of migrating existing interfaces into a lambda style has been solved by default methods, and in this article I will delve into bulk operations in Java collections to unlock the mysteries of lambda's greatest power.

1. About JSR335

JSR is short for Java Specification Requests, which stands for Java Specification Requests, and the major improvement in Java 8 is project Lambda (JSR 335), which aims to make Java easier to code for multi-core processors. JSR 335=lambda expression + interface improvement (default method) + batch data operation. In addition to the previous two articles, we have learned the relevant content of JSR335 completely.

2. External VS internal iteration

Previously, Java collections could not express internal iterations, but only provided a way to iterate externally, namely for or while loops.


List persons = asList(new Person("Joe"), new Person("Jim"), new Person("John"));
for (Person p :  persons) {
   p.setLastName("Doe");
}

The above example is what we did before, which is called an external iteration, where the loop is a fixed sequential loop. In today's multi-core world, we have to change this code if we want to do parallel loops. How much efficiency can be improved is an open question, and there are risks (thread-safety issues, etc.).

To describe the inner iteration, we need class libraries like Lambda, so let's rewrite the loop above using Lambda and collection.foreach

persons.forEach(p->p.setLastName("Doe"));

Now that the JDK library controls the loop, we don't need to worry about how the last name is set into each person object. The library can decide what to do, in parallel, out of order, or lazily loaded, depending on the runtime. This is the internal iteration, in which the client passes the behavior p.sitlastname into the API as data.

The internal iteration is not really closely related to the batch operation of the collection, with which we feel the change in syntax expression. The really interesting thing about batch operations is the new stream API. The new java.util. Stream package has been added to JDK 8.

3. The Stream API

A Stream simply represents a Stream of data, and it has no data structure, so it can't be iterated over once (which is something to be aware of when programming, unlike a Collection, which has data in it for as many times as possible). Instead, it can be sourced from a Collection, array, IO, and so on.

3.1 intermediate and endpoint methods

Flow is to provide an interface to operate big data, making data operations easier and faster. There are two kinds of methods: filtering, mapping, and reducing traversal. There are intermediate methods and terminal methods. The "Stream" abstraction is inherently continuous. The way to distinguish between the two is to look at its return value, if it's a Stream it's an intermediate method, otherwise it's an end method. Please refer to the Stream API for details.

Several intermediate methods (filter, map) and endpoint methods (collect, sum) are briefly introduced.

3.1.1 Filter

Implementing filtering in a data stream is the first and most natural operation we can think of. The Stream interface exposes a filter method that can accept the Predicate implementation that represents the operation to use the lambda expression that defines the filter condition.


List persons =  ... 
Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//Filter out people over 18

3.1.2 the Map

Suppose we now filter some data, such as when converting objects. The Map operation allows us to execute an implementation of Function (Function< T, R> The generic T and R represent the execution input and the execution result respectively), which accept the input and return. First, let's look at how to describe it in terms of anonymous inner classes:


Stream adult= persons
              .stream()
              .filter(p -> p.getAge() > 18)
              .map(new Function() {
                  @Override
                  public Adult apply(Person person) {
                     return new Adult(person);//Turning people over 18 into adults
                  }
              });

Now, turn the above example into a lambda expression:


Stream map = persons.stream()
                    .filter(p -> p.getAge() > 18)
                    .map(person -> new Adult(person));

3.1.3 the Count

The count method is an end of the flow method, which can make the final count of the result of the flow and return an int. For example, let's calculate the total number of people that satisfy 18 years old:


int countOfAdult=persons.stream()
                       .filter(p -> p.getAge() > 18)
                       .map(person -> new Adult(person))
                       .count();

3.1.4 Collect

The collect method is also an endpoint of the flow that collects the final results


List adultList= persons.stream()
                       .filter(p -> p.getAge() > 18)
                       .map(person -> new Adult(person))
                       .collect(Collectors.toList());

Or, if we want to use a specific implementation class to collect results:


List adultList = persons
                 .stream()
                 .filter(p -> p.getAge() > 18)
                 .map(person -> new Adult(person))
                 .collect(Collectors.toCollection(ArrayList::new));

Space is limited, other intermediate methods and end methods are not introduced, looked at the above examples, we understand the difference between the two methods can be, can be decided according to the need to use.

3.2 sequential and parallel flows

Each Stream has two modes: sequential execution and parallel execution.
Order flow:


List <Person> people = list.getStream.collect(Collectors.toList());

Parallel flows:

List <Person> people = list.getStream.parallel().collect(Collectors.toList());

As the name implies, when a sequence is used, each item is read before the next item is read. Using parallel traversal, the array is divided into segments, each of which is processed in a different thread, and the results are printed out together.

3.2.1 principle of parallel flow:


List originalList = someData;
split1 = originalList(0, mid);//Divide the data into smaller pieces
split2 = originalList(mid,end);
new Runnable(split1.process());//Small number of operations
new Runnable(split2.process());
List revisedList = split1 + split2;//Merge the results

3.2.2 comparison of sequential and parallel performance tests

If you are a multi-core machine, the parallel stream is theoretically twice as fast as the sequential stream. Here is the test code


long t0 = System.nanoTime();
//Initialize a stream of integers with a range of 1 million to find Numbers divisible by 2. ToArray () is the endpoint method
int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();
long t1 = System.nanoTime();
//As above, parallel flow is used to calculate
int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();
long t2 = System.nanoTime();
//The result of my machine is serial: 0.06s, parallel 0.02s, proving that parallel streams are indeed faster than sequential streams
System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);

3.3 about the Folk/Join framework

One of the new features of the java.util.concurrent package is a fork-join style parallel decomposition framework, which is also powerful and efficient. I won't go into details here, but I prefer stream.parallel () to stream.parallel ().

4. To summarize

Without lambda, Stream is quite cumbersome to use. It produces a lot of anonymous inner classes, such as the 3.1.2map example above. Without default method, changes to the collection framework are bound to cause a lot of changes.


Related articles: