English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

In-Depth Analysis of JDK8New Features - Lambda Expressions

The first time I encountered Lambda expressions was in TypeScript (a superset of JavaScript), at that time it was to make the this method in TypeScript external rather than internal to the method. After using it, I suddenly thought that Lambda is not JDK8a new feature with high weight? So I feel like looking up related materials and recording them down:

1. Behavior Parameterization

Behavior parameterization, simply put, means that the body of the function only contains general code of template classes, and some logic that changes with the business scenario is passed to the function in the form of parameters. Using behavior parameterization can make the program more general, to meet the frequent change requirements.

Consider a business scenario, suppose we need to filter apples through the program, we first define an entity for Apple:

public class Apple {
/** ID */
private long id;
/** Color */
private Color color;
/** Weight */
private float weight;
/** 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 requirement of the user may be just to be able to filter out green apples through the program, so we can quickly implement it 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, there is nothing to say. But when the user's requirement changes to green, it seems that modifying the code is also very simple, just change the judgment condition from green to red. But we need to consider another problem, what if the change condition changes frequently?63;If it is just a change in color, then it is good for us to directly let the user pass in the judgment condition of color, and the parameter of the judgment method changes to "the set to be judged and the color to be filtered". But if the user wants to judge not only color, but also weight, size, and so on, what should we do? Do you think that adding different parameters in turn to complete the judgment is okay? But is it really good to pass parameters in this way? If the filtering conditions become more and more, and the combination pattern becomes more and more complex, do we need to consider all possible situations and have corresponding strategies for each case?63;At this time, we can parameterize the behavior, extract the filtering conditions as parameters to be passed in, and at this time we can encapsulate a judgment interface out:

public interface AppleFilter {
/**
* Abstract filtering condition
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}
/**
* Encapsulate the filtering conditions into an interface
*
* @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 abstracting the behavior above, we can set the filtering conditions at the specific call location and pass the conditions as parameters to the method. At this time, we use the method of anonymous inner class:

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

Such a design is often used internally in JDK, such as Java.util.Comparator, java.util.concurrent.Callable, etc. When using such interfaces, we can specify the specific execution logic of the function at the place of specific calls using anonymous classes. However, from the above code block, although it is very geeky, it is not concise enough. In java8We can simplify it through lambda:

// Filter apples
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
//()->xxx () inside is the method parameter, xxx is the method implementation

2. Definition of lambda expression

We can define lambda expressions as concise, passable anonymous functions. First, we need to clarify that lambda expressions are essentially functions, although they do not belong to a specific class, they have parameter lists, function bodies, return types, and can throw exceptions; secondly, they are anonymous, lambda expressions do not have specific function names; lambda expressions can be passed like parameters, which greatly simplifies code writing. The format definition is as follows:

Format one: Parameter list -> Expression

Format two: Parameter list -> {Set of expressions}

It should be noted that lambda expressions implicitly contain the return keyword, so we do not need to explicitly write the return keyword in a single expression. However, when the expression is a set of statements, we need to explicitly add return, and enclose multiple expressions with curly braces { }. Below are some examples:

//Returns the length of the given string, implicitly containing a return statement
(String s) -> s.length() 
// Always returns42of the no-argument method
() -> 42 
// If there are multi-line expressions, they should be enclosed in curly braces
(int x, int y) -> {
int z = x * y;
return x + z;
}

3. Using lambda expressions based on functional interfaces

The use of lambda expressions requires the assistance of functional interfaces, which means that lambda expressions can only be simplified in places where functional interfaces appear.

Custom functional interface:

The functional interface is defined as an interface that has only one abstract method. java8The improvement on the interface definition is the introduction of default methods, which allows us to provide a default implementation of the method within the interface. However, regardless of how many default methods exist, as long as there is one and only one abstract method, it is a functional interface, as shown in the above AppleFilter (reference):

/**
* Apple filtering interface
*/
@FunctionalInterface
public interface AppleFilter {
/**
* Abstract filtering condition
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}

AppleFilter only contains one abstract method accept(Apple apple), which can be regarded as a functional interface according to the definition. When defining the interface, we added the @FunctionalInterface annotation to mark it as a functional interface. However, this annotation is optional. After adding the interface, the compiler restricts the interface to only allow one abstract method, otherwise an error is reported, so it is recommended to add this annotation to the functional interface.

The built-in functional interfaces of JDK:

The JDK has built-in rich functional interfaces for lambda expressions. Below, we will explain the usage examples of Predicate<T>, Consumer<T>, and Function<T, R>.

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 the above AppleFilter, which validates the input parameters based on the conditions set externally and returns the validation result boolean. Below, we use Predicate to filter the elements of the List collection:

/**
*
* @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

@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}

Consumer provides an abstract function accept, which receives parameters but does not return a value. Below, we use Consumer to iterate through the collection.

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

Using the above functional interface, iterate through the string collection and print non-empty strings:

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

Function

@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}

The Funcation performs a transformation operation, taking data of type T as input and returning data of type R. Below, we use Function to transform the collection:

public <T, R> List<R> filter(List<T> list, Function<T, R> function) {
List<R> newList = new ArrayList<R>();
for (final T t : list) {
newList.add(function.apply(t));
}
return newList;
}

Other:

demo.filter(list, (String str -> Integer.parseInt(str));

The above functional interfaces also provide some default implementations of logical operations, which will be introduced later in Java8The default methods of the interface will be discussed later~

Some things to pay attention to during use:

Type inference:

During the coding process, sometimes we may wonder which functional interface our calling code will match specifically. In fact, the compiler will make the correct judgment based on the parameters, return type, exception type (if any), etc.
During the specific call, in some cases, the parameter type can be omitted to further simplify the code:

// Filter apples
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
// In some cases, we can even omit the parameter type, and the compiler will correctly judge according to the context
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.R
> ED.equals(apple.getColor()) && apple.getWeight() >= 100);

Local variable

In all the examples above, our lambda expressions use their subject parameters. We can also use local variables in lambda expressions, as follows

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

In this example, we use a local variable weight in the lambda expression. However, in order to use local variables in a lambda expression, the variable must be explicitly declared as final or effectively final. This is mainly because local variables are stored on the stack, while lambda expressions run in another thread. When this thread tries to access the local variable, there is a possibility that the variable may be changed or reclaimed, so using the final modifier can avoid thread safety issues.

4. Method References

Using method references can further simplify the code, and sometimes this simplification makes the code look more intuitive. Let's look at an example first:

/* ... Omitting the initialization operation of apples */
// Using lambda expressions
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
// Using method reference
apples.sort(Comparator.comparing(Apple::getWeight));

Method references connect the method membership and the method itself through ::, and are mainly divided into three categories:

Static method

(args) -> ClassName.staticMethod(args)

Convert to

ClassName::staticMethod

Instance method of parameters

(args) -args.instanceMethod()

Convert to

> ClassName::instanceMethod // ClassName is the type of args

External instance method

(args) -> ext.instanceMethod(args)

Convert to

ext::instanceMethod(args)

Reference:

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

The above is what the editor introduces to everyone about JDK8New feature: Lambda expression, hoping it will be helpful to everyone. If you have any questions, please leave a message, and the editor will reply to everyone in time. I also want to express my sincere gratitude to everyone for their support of the Yell tutorial website!

Declaration: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously, and this website does not own the copyright. It has not been manually edited and does not assume relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#w3Please replace # with @ when sending an email to report infringement, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.

You May Also Like