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 this
functions 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
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();
}
}
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.funcion
package, 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:
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.function
the 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 Collection
the subclass as an example to illustrate. Understanding the principle of Java7 implementation will help to understand the following.List
List
ArrayList
ArrayList
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).action
Consumer
void 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 Comsumer
the 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 Consumer
the 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 filter
the specified conditions . It Predicate
is 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 Precicate
interfaces 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的元素
Predicate
There 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 operator
the specified operation on each element and replace the original element with the result of the operation . Among them UnaryOperator
is 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 UnaryOperator
the 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 List
the interface, the method signature is void sort(Comparator<? super E> c)
, this method sorts the container elements according toc
the specified comparison rules . Comparator
We 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 Collections
the 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 Iterator
it is used to iterate the container Spliterator
and has a similar effect, but the differences between the two are as follows:
Spliterator
EitherIterator
iterate one by one like that, or iterate in batches. Batch iteration can reduce the overhead of iteration.Spliterator
is splittable, oneSpliterator
can try to split into two by callingSpliterator<T> trySplit()
the method. One isthis
, 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.Stream
parallelStream()
Stream
Stream
New methods in Map
Compared with Collection
, Map
more methods have been added, and we will use them as HashMap
examples to explore the secrets one by one. Understanding [Java7 HashMap
Implementation 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 ,Map
action
which BiConsumer
is a function interface, and there is a method to be implemented in it void accept(T t, U u)
. BinConsumer
Interface 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 BiConsumer
the 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 key
query , and return if not foundMap
value
defaultValue
. 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 key
mapping 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.value
Map
Map
remove()
We all know Map
that there is a remove(Object key)
method to delete the mapping relationship in according to the specified key
value Map
; Java8 has added a new remove(Object key, Object value)
method, only when the current Map
** key
just maps to value
** will delete the mapping, otherwise it will do nothing.
replace()
In Java7 and before, Map
the 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 Map
has 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 currentMap
** existskey
, otherwise it does nothing.value
replace(K key, V oldValue, V newValue)
, only if the currentMap
**key
mapping exists and is equal tooldValue
**, it isnewValue
used 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 Map
each 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.function
function
value
BiFunction
R 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 BiFunction
the 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:
- If the corresponding mapping
Map
in does not exist or is , then (cannot be ) is associated with it;key
null
value
null
key
- Otherwise execute
remappingFunction
, if the execution result is not then associatenull
with the resultkey
, otherwiseMap
deletekey
the mapping in .
The function interface in the parameters BiFunction
has 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 remappingFunction
associate the calculation result key
of , if the calculation result is null
, Map
delete key
the 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.key
null
mappingFunction
mappingFunction
null
key
Function
Is a functional interface, which has a method to be implemented R apply(T t)
.
computeIfAbsent()
Commonly used to create an initialization map for Map
a key
value of . For example, if we want to implement a multi-value mapping, Map
the definition may be that Map<K,Set<V>>
to Map
put 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.key
null
remappingFunction
remappingFunction
null
key
key
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;
- 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.
- 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