An in depth analysis of the Lambda expression for the new JDK8 feature

  • 2020-05-12 02:36:24
  • OfStack

The first time I touched the Lambda expression was in TypeScript (a superset of JavaScript), in order to have it used outside the this method of TypeScript rather than inside this method. After using it, it suddenly occurred to me that Lambda is not a heavyweight new feature of JDK8? So I felt like looking up relevant information and writing it down:

1. Behavior parameterization

Behavior parameterization simply means that the body of the function only contains the generic code of the template class, while some logic that will change with the business scenario is passed to the function in the form of parameters. The adoption of behavior parameterization can make the program more generic to cope with the frequently changing requirements.

Considering 1 business scenario, suppose we need to filter apple through the program, we first define the entity of 1 apple:


public class Apple {
/**  Serial number  */
private long id;
/**  color  */
private Color color;
/**  The weight of the  */
private float weight;
/**  place of origin  */
private String origin;
public Apple() {
}
public Apple(long id, Color color, float weight, String origin) {
this.id = id;
this.color = color;
this.weight = weight;
this.origin = origin;
}
//  omit getter and setter
}

The initial requirements of users may simply be to screen out green apples through the program, so we can quickly achieve through the program:


public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}

This code is very simple, nothing to say. But when the user needs to change to green, it seems easy to change the code, just by changing the green of the criteria to red. But we need to consider another problem, if the conditions change frequently. If it's just a color change, then we can directly ask the user to pass in the color judgment condition, and the parameters of the judgment method change to "the set to be judged and the color to be filtered". But what if the user wants to judge not just the color, but the weight, the size, and so on? Do you think we should just add different parameters to complete the judgment? But is this really a good way to pass parameters? If there are more and more filtering conditions and more and more complex combination patterns, do we need to take all the cases into account and have a strategy for each one? At this time, we can parameterize the behavior, extract the filter conditions and pass them in as parameters. At this time, we can encapsulate a judgment interface:


public interface AppleFilter {
/**
*  Filter condition abstraction 
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}
/**
*  Encapsulate filter conditions as interfaces 
*
* @param apples
* @param filter
* @return
*/
public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (filter.accept(apple)) {
filterApples.add(apple);
}
}
return filterApples;
}

After the abstraction of the above behavior, we can set the filter condition at the specific place of the call and pass the condition as a parameter to the method. At this time, the method of the anonymous inner class is adopted:


public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
//  Screening of apple 
List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() {
@Override
public boolean accept(Apple apple) {
//  Screening weight greater than 100g The red apple 
return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100;
}
});
}

Such a design within the jdk also often use, such as Java. util. Comparator, java. util. concurrent. Callable etc., using the 1 class interface, the place where we can be in specific call used anonymous class to specify the specific execution of function logic, but from the above code blocks, although very geek, but not enough concise, we can be simplified by lambda in java8:


//  Screening of apple 
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
//()->xxx () Inside are the method parameters, xxx Method implementation 

2. Definition of lambda expression

We can define lambda expression as a kind of concise, passable anonymous function. First, we need to make it clear that lambda expression is essentially a function. Although it does not belong to a particular class, it has a parameter list, function body, return type, and the ability to throw exceptions. Second, it is anonymous. The lambda expression has no specific function name. The lambda expression can be passed as parameter 1, greatly simplifying the writing of the code. The format is defined as follows:

Format 1: parameter list - > expression

Format 2: parameter list - > {expression set}

It is important to note that the lambda expression implies the return keyword, so we do not need to explicitly write the return keyword in a single expression, but when the expression is a collection of 1 statements, we need to explicitly add return and enclose multiple expressions with curly braces {}. Here are some examples:


// Returns the length of a given string, implicitly return statements 
(String s) -> s.length() 
//  Always return 42 No parameters method 
() -> 42 
//  Contains a multiline expression, enclosed in curly braces 
(int x, int y) -> {
int z = x * y;
return x + z;
}

3. Use lambda expression based on functional interface

The use of lambda expressions requires the help of a functional interface, which means that we can simplify the lambda expression only where the functional interface appears.

Custom functional interface:

A functional interface is defined as one that has only one abstract method. The interface definition improvement of java8 is the introduction of default methods, so that we can provide a default implementation of methods in the interface, but no matter how many default methods exist, as long as there is one and only one abstract method, it is a functional interface, as follows (reference to AppleFilter above) :


/**
*  Apple filter interface 
*/
@FunctionalInterface
public interface AppleFilter {
/**
*  Filter condition abstraction 
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}

AppleFilter accept contains only one abstract method (Apple apple), according to the definition can be regarded as a functional interface, when we define for the interface we add @ FunctionalInterface annotations, used to mark the interface is functional interface, this interface is optional, but when adding the interface, the compiler will limit the interface allows only one abstract method, otherwise an error, so the recommended for functional interface to add the annotation.

jdk's built-in functional interface:

The jdk for lambda expression has built-in rich functional interfaces, the following are Predicate < T > , Consumer < T > , Function < T, R > The use examples are illustrated.

Predicate:


@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}

The function of Predicate is similar to that of AppleFilter above. It verifies the incoming parameters according to the conditions we set externally, and returns the verification result boolean. Predicate is used below to filter the elements of List set:


/**
*
* @param list
* @param predicate
* @param <T>
* @return
*/
public <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> newList = new ArrayList<T>();
for (final T t : list) {
if (predicate.test(t)) {
newList.add(t);
}
}
return newList;
}

Use:


demo.filter(list, (String str) -> null != str && !str.isEmpty());

Consumer


public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}
0

Consumer provides an accept abstract function that takes parameters but does not return a value. Consumer is used to traverse the collection.


/**
*  Performs custom behavior by walking through the collection 
*
* @param list
* @param consumer
* @param <T>
*/
public <T> void filter(List<T> list, Consumer<T> consumer) {
for (final T t : list) {
consumer.accept(t);
}
}

Use the above functional interface to traverse the string collection and print a non-empty string:


demo.filter(list, (String str) -> {
if (StringUtils.isNotBlank(str)) {
System.out.println(str);
}
});

Function


public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}
3

Funcation performs conversion operation, inputs data of type T, and returns data of type R. Function is used to convert the collection below:


public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}
4

Other:


public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}
5

These functional interfaces also provide a default implementation of some logical operations, which we'll discuss later when we introduce the default methods of the java8 interface

1 things to pay attention to in the process of use:

Type inference:

During the coding process, we may sometimes be confused about which functional interface our calling code will match. In fact, the compiler will make correct decisions based on parameters, return types, exception types (if any), and so on.
When making a specific call, you can omit the type of the argument at some point to simplify the code one step further:


public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}
6

A local variable

In all of the examples above, our lambda expression USES its body parameter, and we can also use local variables in lambda, as shown below


int weight = 100;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight); : 

In this example we use in lambda weight local variables, but the use of local variables in lambda must demand the variable explicitly declared for final final or in fact, this is mainly because the storage on the stack for local variables, lambda expression is running in the other thread 1, when the thread view access to the local variable, the variable there is the possibility of change or recycling, so use final will not thread safe problems after modification.

4. Method references

Method references can be used to further simplify the code. Sometimes this simplification makes the code look more intuitive. Let's take a look at an example:


/* ...  omit apples Initialization operation  */
//  using lambda expression 
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
//  Adopt method reference 
apples.sort(Comparator.comparing(Apple::getWeight));

Method references are mainly divided into three categories:

A static method


public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}
9

Converted to


ClassName::staticMethod

Parameter's instance method


(args) -> args.instanceMethod()

Converted to


ClassName::instanceMethod // ClassName is args The type of 

External instance methods


(args) -> ext.instanceMethod(args)

Converted to


ext::instanceMethod(args)

Reference:

http://www.codeceo.com/article/lambda-of-java-8.html


Related articles: