Do you know how to use Lambda expressions?

functional programming

Before formally learning Lambda, let's first understand what is functional programming

Let's first look at what a function is. Function is the most basic task. A large-scale program is a top-level function that calls several bottom-level functions, and these called functions can call other functions, that is, large tasks are disassembled and executed layer by layer. So function is the basic unit of procedure-oriented programming.

Java does not support defining functions separately, but you can treat static methods as independent functions and instance methods as thisfunctions with their own parameters. And functional programming (please note that there is an extra word "style") - Functional Programming, although it can also be attributed to process-oriented programming, its idea is closer to mathematical calculation.

We must first understand the concepts of Computer and Compute. At the computer level, the CPU executes the instruction codes for addition, subtraction, multiplication, and division, as well as various conditional judgments and jump instructions. Therefore, assembly language is the language closest to the computer. Computation refers to calculation in the mathematical sense, the more abstract the calculation, the farther it is from the computer hardware. Corresponding to the programming language, the lower the language, the closer to the computer, the lower the abstraction, and the higher the execution efficiency, such as the C language; the higher the level of the language, the closer to the calculation, the higher the abstraction, and the lower the execution efficiency, such as the Lisp language.

Functional programming is a programming paradigm with a high degree of abstraction. Functions written in pure functional programming languages ​​have no variables. Therefore, for any function, as long as the input is certain, the output is certain. We call this pure function for no side effects. However, in programming languages ​​that allow the use of variables, because the state of the variables inside the function is uncertain, the same input may result in different outputs. Therefore, this kind of function has side effects.

One of the features of functional programming is that it allows functions themselves to be passed as arguments to another function, and it also allows a function to be returned!

Functional programming was originally a set of function transformation logic studied by mathematician Alonzo Church , also known as Lambda Calculus (λ-Calculus), so functional programming is often called Lambda computing.

The Java platform, starting with Java 8, supports functional programming.

First experience with Lambda

Starting with an example, let's see where Lambda can be used.

Example 1: Create a thread

Common methods of creating threads (before JDK1.8)

//JDK1.7通过匿名内部类的方式创建线程
Thread thread = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
     //实现run方法
        System.out.println("Thread Run...");
    }
});

thread.start();

Creating threads by means of anonymous inner classes saves the trouble of naming, but can it be simplified?

JDK1.8 Lambda expression writing method

Thread thread = new Thread(() -> System.out.println("Thread Run")); //一行搞定

thread.start();

We can see that Lambda completes the thread creation with one line of code, which is not too convenient. (As for the syntax of Lambda expressions, we will introduce them in detail in the following chapters)

If your logic is more than one line of code, then you can also write

Thread thread = new Thread(() -> {
    
    
    System.out.println("Thread Run");
    System.out.println("Hello");
});

thread.start();

Wrap {}the code block with

Example 2: Custom Comparator

Let's first look at how JDK1.7 implements a custom comparator

List<String> list = Arrays.asList("Hi", "Life", "Hello~", "World");
Collections.sort(list, new Comparator<String>(){
    
    // 接口名
    @Override
    public int compare(String s1, String s2){
    
    // 方法名
        if(s1 == null)
            return -1;
        if(s2 == null)
            return 1;
        return s1.length()-s2.length();
    }
});

//输出排序好的List
for (String s : list) {
    
    
    System.out.println(s);
}

The sort method here passes in two parameters, one is the list to be sorted, and the other is a comparator (sorting rule), which is also implemented by means of an anonymous inner class.

Let's take a look at how Lambda expressions implement comparators?

List<String> list = Arrays.asList("Hi", "Life", "Hello~", "World");
Collections.sort(list, (s1, s2) ->{
    
    // 省略了参数的类型,编译器会根据上下文信息自动推断出类型
    if(s1 == null)
        return -1;
    if(s2 == null)
        return 1;
    return s1.length()-s2.length();
});

//输出排序好的List
for (String s : list) {
    
    
    System.out.println(s);
}

We can see that Lambda expressions have the same effect as anonymous inner classes, but a lot of code is omitted, which can greatly speed up development

Lambda expression syntax

Lambda expressions, also known as closures, are the most important new feature driving the release of Java 8. Lambda allows functions to be passed as parameters of a method (functions are passed as parameters into methods).

Using Lambda expressions can make the code more concise and compact. In the previous chapter, we have seen the advantages of Lambda expressions, so how should Lambda expressions be written?

grammar

The syntax of a lambda expression is as follows:

(parameters) -> expression   //一行代码(parameters) ->{
    
     statements; }  //多行代码

Important features of lambda expressions:

  • **Optional type declaration:** There is no need to declare the parameter type, and the compiler can uniformly identify the parameter value.
  • **Optional parameter parentheses: **One parameter does not need to define parentheses, but multiple parameters need to define parentheses.
  • **Optional curly braces:** If the body contains a statement, curly braces are not required.
  • **Optional return keyword: **If the body has only one expression return value, the compiler will automatically return the value, and the curly braces need to specify that the expression returns a value.
// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

functional interface

The above chapters introduced the basic use of Lambda expressions, so can Lambda expressions be used anywhere?

In fact, the use of Lambda expressions is limited. Maybe you have already thought that the basis for using Lambda is that there must be a corresponding functional interface . (Functional interface refers to an interface with only one abstract method inside)

custom function interface

Custom functional interfaces are easy, you just need to write an interface with only one abstract method.

// 自定义函数接口
@FunctionalInterface
public interface PersonInterface<T>{
    
    
    void accept(T t);
}

The @FunctionalInterface in the above code is optional, but with this annotation the compiler will help you check whether the interface conforms to the functional interface specification. Just like adding the @Override annotation will check whether the function is overloaded.

Then according to the custom functional interface above, we can write the following Lambda expression.

PersonInterface p = str -> System.out.println(str);

Lambdas and anonymous inner classes

After the introduction of the above parts, I believe that everyone has a preliminary understanding of Lambda expressions and learned how to use them. But there must always be a question in everyone's mind. Lambda expressions seem to be just to simplify the writing of anonymous inner classes, and there is no difference in other things. It seems that it is enough to replace all lambda expressions with anonymous inner classes at compile time through syntactic sugar. Is this really the case?

public class Main {
    
    

    public static void main(String[] args) {
    
    
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("Anonymous class");
            }
        }).start();
    }
}

An anonymous inner class is also a class, but we don't need to explicitly define a name for him, but the compiler will automatically name the anonymous inner class. The main edited file is as shown below

image-20201217164035157

We can see that there are two class files, one is Main.class, and the other is the inner class named for us by the editor.

Let's take a look at how many class files the Lambda expression will generate

public class Main {
    
    
    public static void main(String[] args) {
    
    
        new Thread(() -> System.out.println("Lambda")).start();
    }
}

image-20201217164610350

Lambda expressions are implemented through the invokedynamic instruction, writing Lambda expressions will not generate new classes

The use of Lambda in collections

Since Lambda expressions are so convenient, where can Lambda expressions be used?

Let's start with the most familiar *Java Collections Framework (JCF)*.

In order to introduce Lambda expressions, Java8 has added a new java.util.funcionpackage, which contains commonly used function interfaces , which are the basis of Lambda expressions. The Java collection framework also adds some interfaces to interface with Lambda expressions.

First review the interface inheritance structure of the Java collection framework:

JCF_Collection_Interfaces

The interface classes marked in green in the above figure indicate that new interface methods have been added in Java8. Of course, due to the inheritance relationship, their corresponding subclasses will also inherit these new methods. The table below details these methods.

interface name Java8 newly added method
Collection removeIf() spliterator() stream() parallelStream() forEach()
List replaceAll() sort()
Map getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()

Most of these newly added methods use java.util.functionthe interfaces under the package, which means that most of these methods are related to Lambda expressions. We will learn these methods one by one.

New methods in Collection

As shown above, some methods have been newly added to the interface and we will use Collectionthe subclass as an example to illustrate. Understanding the principle of Java7 implementation will help to understand the following.ListListArrayListArrayList

forEach()

The signature of this method is to perform the specified action void forEach(Consumer<? super E> action)on each element in the container , which is a function interface, and there is only one method to be implemented (we will see later that it does not matter what this method is called, you don’t even need remember its name).actionConsumervoid accept(T t)

Requirement: Suppose there is a list of strings, and all strings whose length is greater than 3 need to be printed out.

Java7 and before we can use the enhanced for loop to achieve:

// 使用曾强for循环迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(String str : list){
    
    
    if(str.length()>3)
        System.out.println(str);
}

Now using forEach()methods combined with anonymous inner classes, this can be achieved like this:

// 使用forEach()结合匿名内部类迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){
    
    
    @Override
    public void accept(String str){
    
    
        if(str.length()>3)
            System.out.println(str);
    }
});

The above code calls forEach()the method and implements Comsumerthe interface using an anonymous inner class. So far we haven't seen any benefits of this design, but don't forget about Lambda expressions, using Lambda expressions as follows:

// 使用forEach()结合Lambda表达式迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach( str -> {
    
    
        if(str.length()>3)
            System.out.println(str);
    });

The above code forEach()passes a Lambda expression to the method. We don't need to know accept()the method or Consumerthe interface. The type deduction does everything for us.

removeIf()

The signature of this method boolean removeIf(Predicate<? super E> filter)is to delete all elements in the container that meet filterthe specified conditions . It Predicateis a function interface, and there is only one method to be implemented boolean test(T t). The name of the same method is not important at all, because it does not need to be written when using it.

Requirement: Suppose there is a list of strings, and all strings whose length is greater than 3 need to be deleted.

We know that if you need to delete the container during the iteration process, you must use an iterator, otherwise it will be thrown ConcurrentModificationException, so the traditional way of writing the above task is:

// 使用迭代器删除列表元素
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Iterator<String> it = list.iterator();
while(it.hasNext()){
    
    
    if(it.next().length()>3) // 删除长度大于3的元素
        it.remove();
}

Now using removeIf()methods combined with anonymous inner classes, we can do this:

// 使用removeIf()结合匿名名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate<String>(){
    
     // 删除长度大于3的元素
    @Override
    public boolean test(String str){
    
    
        return str.length()>3;
    }
});

The above code uses removeIf()methods, and implements Precicateinterfaces using anonymous inner classes. I believe you have already thought of how to write in Lambda expressions:

// 使用removeIf()结合Lambda表达式实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(str -> str.length()>3); // 删除长度大于3的元素

PredicateThere is no need to remember the interface name or the method name when using Lambda expressions test(). You only need to know that a Lambda expression that returns a Boolean type is needed here.

replaceAll()

The signature of this method void replaceAll(UnaryOperator<E> operator)is to perform operatorthe specified operation on each element and replace the original element with the result of the operation . Among them UnaryOperatoris a function interface, which has only one function to be implemented T apply(T t).

Requirement: Suppose there is a list of strings, convert all the elements whose length is greater than 3 to uppercase, and keep the rest of the elements unchanged.

There seems to be no elegant way in Java7 and before:

// 使用下标实现元素替换
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(int i=0; i<list.size(); i++){
    
    
    String str = list.get(i);
    if(str.length()>3)
        list.set(i, str.toUpperCase());
}

Using replaceAll()methods combined with anonymous inner classes can be achieved as follows:

// 使用匿名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(new UnaryOperator<String>(){
    
    
    @Override
    public String apply(String str){
    
    
        if(str.length()>3)
            return str.toUpperCase();
        return str;
    }
});

The above code calls replaceAll()the method and implements UnaryOperatorthe interface using an anonymous inner class. We know that it can be implemented with a more concise Lambda expression:

// 使用Lambda表达式实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(str -> {
    
    
    if(str.length()>3)
        return str.toUpperCase();
    return str;
});

sort()

This method is defined in Listthe interface, the method signature is void sort(Comparator<? super E> c), this method sorts the container elements according toc the specified comparison rules . ComparatorWe are not unfamiliar with interfaces, and there is a method that int compare(T o1, T o2)needs to be implemented. Obviously, the interface is a functional interface.

Requirements: Suppose there is a list of strings, and the elements are sorted in ascending order of string length.

Since Java7 and previous sort()methods are in Collectionsthe tool class, the code should be written like this:

// Collections.sort()方法
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Collections.sort(list, new Comparator<String>(){
    
    
    @Override
    public int compare(String str1, String str2){
    
    
        return str1.length()-str2.length();
    }
});

Now it can be used directly List.sort()方法, combined with Lambda expressions, it can be written like this:

// List.sort()方法结合Lambda表达式
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length()-str2.length());

spliterator()

The method signature is Spliterator<E> spliterator(), which returns a splittable iterator of the container . From the name, this method iterator()is a bit similar to the method. We know that Iteratorit is used to iterate the container Spliteratorand has a similar effect, but the differences between the two are as follows:

  1. SpliteratorEither Iteratoriterate one by one like that, or iterate in batches. Batch iteration can reduce the overhead of iteration.
  2. Spliteratoris splittable, one Spliteratorcan try to split into two by calling Spliterator<T> trySplit()the method. One is this, and the other is the newly returned one, the elements represented by these two iterators do not overlap.

The load can be broken up by (multiple) Spliterator.trySplit()method calls for multithreading.

stream() and parallelStream()

stream()and returns the view representationparallelStream() of this container separately , except that returns in parallel . It is the core class of Java functional programming , which we will learn in the following chapters.StreamparallelStream()StreamStream

New methods in Map

Compared with Collection, Mapmore methods have been added, and we will use them as HashMapexamples to explore the secrets one by one. Understanding [Java7 HashMapImplementation Principle](https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/6-HashSet and HashMap.md) will help to understand the following.

forEach()

The signature of this method void forEach(BiConsumer<? super K,? super V> action)is to perform the specified operation on each mapping in it ,Mapaction which BiConsumeris a function interface, and there is a method to be implemented in it void accept(T t, U u). BinConsumerInterface names and accept()method names are not important, please don't memorize them.

Requirements: Suppose there is a Map from a number to a corresponding English word, please output all the mappings in the Map.

The Java7 and previous classic codes are as follows:

// Java7以及之前迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){
    
    
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

The usage Map.forEach()method, combined with the anonymous inner class, the code is as follows:

// 使用forEach()结合匿名内部类迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach(new BiConsumer<Integer, String>(){
    
    
    @Override
    public void accept(Integer k, String v){
    
    
        System.out.println(k + "=" + v);
    }
});

The above code calls forEach()the method and implements BiConsumerthe interface using an anonymous inner class. Of course, no one uses anonymous inner class writing in actual scenarios, because there are Lambda expressions:

// 使用forEach()结合Lambda表达式迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach((k, v) -> System.out.println(k + "=" + v));
}

getOrDefault()

This method has nothing to do with Lambda expressions, but it is useful. The signature of the method is V getOrDefault(Object key, V defaultValue), the function is to correspond to the given keyquery , and return if not foundMapvaluedefaultValue . Using this method, programmers can save the trouble of querying whether the specified key exists.

Requirements; suppose there is a map from a number to the corresponding English word, output the English word corresponding to 4, and output NoValue if it does not exist

// 查询Map中指定的值,不存在时使用默认值
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
// Java7以及之前做法
if(map.containsKey(4)){
    
     // 1
    System.out.println(map.get(4));
}else{
    
    
    System.out.println("NoValue");
}
// Java8使用Map.getOrDefault()
System.out.println(map.getOrDefault(4, "NoValue")); // 2

putIfAbsent()

This method has nothing to do with Lambda expressions, but it is useful. The signature of the method is V putIfAbsent(K key, V value), the function is to put the specified value in only when there is no keymapping or mapping value of the valuenull , otherwise it will not be changed. This method combines condition judgment and assignment into one, which is more convenient to use.valueMapMap

remove()

We all know Mapthat there is a remove(Object key)method to delete the mapping relationship in according to the specified keyvalue Map; Java8 has added a new remove(Object key, Object value)method, only when the current Map** keyjust maps to value** will delete the mapping, otherwise it will do nothing.

replace()

In Java7 and before, Mapthe mapping relationship in the replacement can put(K key, V value)be realized through the method, which will always replace the original value with the new value. In order to control the replacement behavior more precisely, Java8 Maphas added two replace()methods, which are as follows:

  • replace(K key, V value), which is used to replace the original value only if the mapping in the current Map** existskey , otherwise it does nothing.value
  • replace(K key, V oldValue, V newValue), only if the current Map** keymapping exists and is equal to oldValue**, it is newValueused to replace the original value, otherwise it does nothing.

replaceAll()

The signature of this method replaceAll(BiFunction<? super K,? super V,? extends V> function)is to perform the specified operation on Mapeach mapping in and replace the original one with the execution result . Among them is a function interface, and there is a method to be implemented in it . Don't be intimidated by so many functional interfaces, because you don't need to know their names when using them.functionfunctionvalueBiFunctionR apply(T t, U u)

Requirements: Suppose there is a map from numbers to corresponding English words, please convert the words in the original mapping relationship to uppercase.

The Java7 and previous classic codes are as follows:

// Java7以及之前替换所有Map中所有映射关系
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){
    
    
    entry.setValue(entry.getValue().toUpperCase());
}

Using replaceAll()methods combined with anonymous inner classes, the implementation is as follows:

// 使用replaceAll()结合匿名内部类实现
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll(new BiFunction<Integer, String, String>(){
    
    
    @Override
    public String apply(Integer k, String v){
    
    
        return v.toUpperCase();
    }
});

The above code calls replaceAll()the method and implements BiFunctionthe interface using an anonymous inner class. Further, using Lambda expression to achieve as follows:

// 使用replaceAll()结合Lambda表达式实现
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll((k, v) -> v.toUpperCase());

It's unbelievably simple.

merge()

The method signature merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)is as follows:

  1. If the corresponding mapping Mapin does not exist or is , then (cannot be ) is associated with it;keynullvaluenullkey
  2. Otherwise execute remappingFunction, if the execution result is not then associate nullwith the result key, otherwise Mapdelete keythe mapping in .

The function interface in the parameters BiFunctionhas been introduced earlier, and there is a method to be implemented in it R apply(T t, U u).

merge()Although the semantics of the method are a bit complicated, the way to use this method is very clear. A common scenario is to splicing new error information to the original information, for example:

map.merge(key, newMsg, (v1, v2) -> v1+v2);

compute()

The signature of this method compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)is to remappingFunctionassociate the calculation result keyof , if the calculation result is null, Mapdelete keythe mapping in .

To implement merge()an example of error message splicing in the above method, use compute()the following code:

map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));

computeIfAbsent()

The signature of this method is V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction), the function is: only when there is no mapping or mapping valueMap in the current value , it is called , and when the execution result is not valid , the result is associated with it.keynullmappingFunctionmappingFunctionnullkey

FunctionIs a functional interface, which has a method to be implemented R apply(T t).

computeIfAbsent()Commonly used to create an initialization map for Mapa keyvalue of . For example, if we want to implement a multi-value mapping, Mapthe definition may be that Map<K,Set<V>>to Mapput a new value into it, it can be realized by the following code:

Map<Integer, Set<String>> map = new HashMap<>();
// Java7及以前的实现方式
if(map.containsKey(1)){
    
    
    map.get(1).add("one");
}else{
    
    
    Set<String> valueSet = new HashSet<String>();
    valueSet.add("one");
    map.put(1, valueSet);
}
// Java8的实现方式
map.computeIfAbsent(1, v -> new HashSet<String>()).add("yi");

Use computeIfAbsent()to combine conditional judgment and adding operation into one to make the code more concise.

computeIfPresent()

The signature of this method is V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction), and the effect is computeIfAbsent()opposite, that is, only when there isMap a mapping of the value in the current and not , it will be called , if the execution result is , delete the mapping, otherwise use the result to replace the original mapping.keynullremappingFunctionremappingFunctionnullkeykey

The function of this function is equivalent to the following code:

// Java7及以前跟computeIfPresent()等效的代码
if (map.get(key) != null) {
    
    
    V oldValue = map.get(key);
    V newValue = remappingFunction.apply(key, oldValue);
    if (newValue != null)
        map.put(key, newValue);
    else
        map.remove(key);
    return newValue;
}
return null;
  1. Java8 adds some useful methods to the container. Some of these methods are to improve the original functions , and some are to introduce functional programming . Learning and using these methods will help us write more concise and effective code.
  2. Although there are many functional interfaces , most of the time we don't need to know their names at all. When writing Lambda expressions, type inference helps us do everything.

Reference: https://github.com/CarpenterLee/JavaLambdaInternals

Guess you like

Origin blog.csdn.net/wgzblog/article/details/112857472