preface
Hello, friends of LearnKu community, long time no see ~
In this article, I will introduce the basic concepts of functional programming, how to use the idea of functional programming to write code, and the basic usage of Java Stream.
This paper will not involve any obscure mathematical concepts, the theory of functional programming and the high-order characteristics of functional programming, such as Lazy Evaluation, pattern matching and so on. Therefore, please rest assured to eat.
This article may be helpful to the following people:
- Someone who can't tell what functional programming is
- People who don't know when to use Java Stream
- Java 8 has been out for so long, but still can't write Stream operation
The code language used in this article is Java, and Python is used in some cases. This article refers to and cites the contents of many excellent articles. All reference links are at the end. If you want to learn more about functional programming and Java Stream related operations, I recommend you to read the materials linked at the end of this article in detail as much as possible. I believe it will be helpful to you: -)
1: Functional programming
1. What is functional programming?
Before we introduce you to functional programming, let's take a brief look at some history.
The theoretical basis of functional programming was developed by Alonzo Church in the 1930s λ Calculus( λ- calculus).
λ The essence of calculus is a mathematical abstraction and a Formal System in mathematical logic. This system is a programming language designed for a super machine. In this language, the parameter of the function is a function and the return value is also a function. This function uses the Greek letter Lambda( λ) To show.
At this time, λ Calculus was only an idea and a computing model of Alonzo, and it was not applied to any hardware system. Until the late 1950s, John McCarthy, a professor of MIT, became interested in Alonzo's research and developed the early functional programming language LISP in 1958. It can be said that LISP language is a kind of Alonzo's language λ The realization of calculus in the real world. Many computer scientists have realized the powerful ability of lisp. In 1973, some programmers in MIT Artificial Intelligence Laboratory developed a machine and called it LISP machine. At this time, Alonzo λ Calculus finally has its own hardware implementation!
Then again, what is functional programming?
In Wikipedia, Functional Programming is defined as follows:
Functional programming is a programming paradigm. It regards calculation as the evaluation of mathematical functions, so as to avoid changing states and using variable data. It is a declarative programming paradigm, which programs through expressions and declarations rather than statements.
At this point, you may still not understand what is FP (Functional Programming)? Since FP is a programming paradigm, you have to mention another programming paradigm corresponding to it - traditional instructive programming (next, we will use some code cases to let you intuitively feel the differences between Functional Programming and instruction programming. In addition, I want to tell you that even if we don't understand the profound concepts in Functional Programming, we can use the idea of Functional Programming to write code:)
Case 1: binary tree image
This topic can be found on LeetCode. Interested friends can search it by themselves.
The title requirements are as follows: Please complete a function, input a binary tree, and the function outputs its image.
For example, enter:
4 / \ 2 7 / \ / \ 1 3 6 9
Mirror output:
4 / \ 7 2 / \ / \ 9 6 3 1
The code of traditional instruction programming is as follows:
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def mirrorTree(self, root: TreeNode) -> TreeNode: if root is None: return None tmp = root.left; root.left = self.mirrorTree(root.right) root.right = self.mirrorTree(tmp); return root
It can be seen that instruction based programming is like an instruction set (action list) specified by our programmers to describe a series of behaviors that the computer needs to make That is, we need to tell the computer in detail what commands need to be executed at each step. Just like this code, we first judge whether the node is empty, then use a temporary variable to store the left subtree, complete the image reversal of the left subtree and the right subtree, and finally switch left and right. We just need to write out the steps that the computer needs to complete, and then give them to the machine to run However, the idea of "machine oriented programming" is instruction programming.
Let's look at functional programming style code:
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def mirrorTree(self, root: TreeNode) -> TreeNode: if root is None: return None return TreeNode(root.val, self.mirrorTree(root.right), self.mirrorTree(root.left))
You may wonder, where does this code reflect functional programming?
Don't worry, let me explain to you slowly. The term function was first used by Leibniz in 1694 to describe the relationship between the change of output value and the change of input value. The Chinese word "function" was translated by Li Shanlan, a mathematician in the Qing Dynasty. In his book algebra, it is explained as: "all functions in this variable (including) Whatever explanation, we all know that the concept of function describes a relational mapping, that is, the corresponding relationship between one thing and another.
Therefore, we can use functional thinking to think and obtain the mirror image of a binary tree. The input of this function is an "original tree" and the return result is a flipped "new tree", and the essence of this function is a mapping from "original tree" to "new tree".
Furthermore, we can find that the mapping relationship is that each node of the "new tree" is recursively opposite to the "original tree".
Although these two pieces of code use recursion, the way of thinking is different. The former describes "how to get a new tree from the original tree", and the latter describes the mapping relationship from the "original tree" to the "new tree".
Case 2: flipping strings
The title is to get the flip of a string.
Imperative programming:
def reverse_string(s): stack = list(s) new_string = "" while len(stack) > 0: new_string += stack.pop() return new_string
This Python code is very simple. We simulate the stack operation of a string from beginning to end, and then the stack operation from end to end. The result is a flipped string.
Functional programming:
def reverse_string(s): if len(s) <= 1: return s return reverse_string(s[1:]) + s[0]
How to understand the idea of functional programming and write the logic of flipping string? Get the inversion of a string. The input of this function is the "original string", and the returned result is the "new string" after inversion. The essence of this function is a mapping from the "original string" to the "new string". The "original string" is divided into the first character and the remaining part, and the remaining part is placed in front after inversion, Then put the first character at the end to get the "new string", which is the mapping relationship between input and output.
Through the above two examples, we can see the ideological differences between instruction programming and functional programming: instruction programming feels like the mathematical problems we solve in primary school, which need to be calculated step by step, and we are concerned about the process of solving problems; Functional programming is concerned with the mapping relationship from data to data.
2. Three features of functional programming
Functional programming has three characteristics:
- immutable data
- first class functions
- "Natural" support for recursion and tail recursion
immutable data
In functional programming, function is the basic unit, and the variables we usually understand are also replaced by functions in functional programming: in functional programming, variables only represent an expression, but I still use the expression "variable" for better understanding.
The functions written by pure functional programming have no "variables", or these "variables" are immutable. This is the first feature of functional programming: immutable data. We can say that for a function, as long as the input is certain, the output is also certain. We call it no side effect. If the state of the internal "variable" of a function is uncertain, the same input may get different outputs, which is not allowed. Therefore, the "variable" here requires that it cannot be modified and can only be assigned an initial value once.
first class functions
In functional programming, functions are the first type of objects. "first class functions" can make your functions be used like "variables". The so-called "function is the first type of object" means that a function can be used not only as the input parameter value of other functions, but also as the output of a function, that is, to return a function from a function.
Let's take an example:
def inc(x): def incx(y): return x+y return incx inc2 = inc(2) inc5 = inc(5) print(inc2(5)) # 7 print(inc5(5)) # 10
In this example, the inc() function returns another function incx(), so we can use the inc() function to construct various versions of Inc functions, such as inc2() and inc5(). This technology is called function Currying. Its essence is to use the "first class functions" feature of functional programming.
"Natural" support for recursion and tail recursion
The idea of recursion is well matched with functional programming. It's a bit like chocolate and music on a rainy day.
Functional programming itself emphasizes the execution result of the program rather than the execution process. Recursion is the same. We care more about the return value of recursion, that is, macro semantics, rather than how it is stacked and nested in the computer.
The classic recursive program case is to implement factorial functions. Here I use JS language:
// Normal recursion const fact = (n) => { if(n < 0) throw 'Illegal input' if (n === 0) return 0 if (n === 1) return 1 return n * fact(n - 1) }
This code can run normally. However, the essence of a recursive program is a method call. When the recursion does not reach the basecase, the method stack will keep pushing into the stack frame. The space of the method stack will not be released until the recursive call has a return value. If the recursive call is very deep, it is easy to cause performance degradation and even stack overflow error.
Tail recursion is a special kind of recursion. "Tail recursion optimization technology" can avoid the above problems and prevent stack overflow.
What is tail recursion? If all recursive calls in a function appear at the end of the function, we call the recursive function tail recursive.
The above code for solving factorials is not tail recursive, because we need a step of calculation after the fact(n - 1) call.
The factorial function of tail recursion is as follows:
// Tail recursion const fact = (n,total = 1) => { if(n < 0) throw 'Illegal input' if (n === 0) return 0 if (n === 1) return total return fact(n - 1, n * total) }
First of all, tail recursive optimization needs the support of language or compiler. For example, Java and Python do not have tail recursive optimization. The reason why they do not do tail recursive optimization is that they can have complete Stack Trace output when throwing exceptions. Languages such as JavaScript and C have the optimization of tail recursion. The compiler can do this because when the compiler detects that the call of a function is tail recursion, it will overwrite the current stack frame instead of pressing a new one in the method stack. Tail recursion greatly reduces the stack memory used by overwriting the current stack frame, and the actual operation efficiency has been significantly improved.
3. Functional programming in Java 8
Functional interface
Java 8 introduces a concept - functional interfaces. The purpose is to make the Java language better support functional programming.
The following is a functional interface:
public interface Action { public void action(); }
A functional interface can only have one abstract method. In addition, this functional interface looks no different from an ordinary interface.
If you want others to immediately understand that this interface is a functional interface, you can add the @ FunctionalInterface annotation, which will not provide any additional functions except to limit and ensure that your functional interface has only one abstract method.
@FunctionalInterface public interface Action { public void action(); }
In fact, there were many functional interfaces long before the emergence of Java 8, such as Runnable, Comparator, InvocationHandler, etc. these interfaces comply with the definition of functional interfaces.
There are several common functional interfaces introduced by Java 8:
- Function<T,R> { R apply(T t); }
- Predicate<T> { boolean test(T t); }
- Consumer<T> { void accept(T t); }
- Supplier<T> { T get(); }
- ... ...
As an aside, I personally hate to explain the usage of API in this article.
- First point: the JDK document has written out the usage of each API in great detail. There is no need to repeat these things again.
- Second point: the number of words in my article is limited. In the limited words, what I express should be something that can guide readers to think and comment, rather than the dross that wastes everyone's reading time.
Therefore, if you want to find out the usage of all functional interfaces, you can consult the documentation by yourself.
Lambda expressions and method references
The function of the following code is to sort the list in the order of string length:
List<String> words = List.of("BBC", "A", "NBA", "F_WORD"); Collections.sort(words, new Comparator<String>() { @Override public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); } });
This code exposes the shortcomings of anonymous classes - lengthy and unclear code meaning. In Java 8, Lambda expressions are introduced to simplify this form of code. If you use IDEA as the code compiler, you will find that after writing this code, the compiler prompts you: anonymous new comparator < string > () can be replaced with Lambda.
When you press the option + enter key, you will find that you have opened the door to a new world: -)
List<String> words = List.of("BBC", "A", "NBA", "F_WORD"); Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
Next, I will not fully explain to you why Lambda expressions can do this:
First of all, the basis for using Lambda expressions is that there must be corresponding functional interfaces, which in turn explains why functional interfaces can only have one abstract method (if there are multiple abstract methods, how does Lambda know what you write).
Second, the Lambda expression should be written like this before being simplified by "type inference":
Collections.sort(words, (String s1, String s2) -> Integer.compare(s1.length(), s2.length()));
The reason why the types in parentheses can be omitted is that Lambda expressions are also based on the type inference mechanism. When the context information is sufficient, the compiler can infer the type of the parameter table without explicit naming. The mechanism of type inference is extremely complex. You can refer to the chapter of type inference introduced by JLS of Java 8. The link is as follows: docs.oracle.com/javase/specs/jls/s....
After converting the anonymous class into Lambda expression, the smart you (actually the smart compiler, doge) found it again, and the compiler continues to prompt you: Can be replaced with Comparator.comparingInt.
We continue to press the option + enter key and find that the Lambda expression is simplified as follows:
Collections.sort(words, Comparator.comparingInt(String::length));
You find that you seem to have entered a new world: -)
The representation of String::length is called method reference, that is, the length() method of String class is called. In fact, Lambda expressions are concise enough, but the meaning of method references is clearer. When using method reference, you only need to use:: Double colon. Both static methods and instance methods can be referenced in this way.
Items 42 and 43 in Effective Java, the Bible of Java practice, are as follows:
- Prefer Lambda s to anonymous classes
- Prefer method references to Lambda s
Start with Java 8. Lambda is by far the best way to represent small function objects. Do not use anonymous classes as function objects unless you must create instances of non functional interface types. Method reference is a further optimization of lambda expression, which has clearer semantics than lambda expression. If method references look shorter and clearer than lambda expressions, use method references!
A case of strategy pattern takes you back to Java functional programming
In this chapter, I have prepared a case of shopping mall discount:
public class PriceCalculator { public static void main(String[] args) { int originalPrice = 100; User zhangsan = User.vip("Zhang San"); User lisi = User.normal("Li Si"); // No discount calculatePrice("NoDiscount", originalPrice, lisi); // 20% off calculatePrice("Discount8", originalPrice, lisi); // Give a 95% discount calculatePrice("Discount95",originalPrice,lisi); // vip discount calculatePrice("OnlyVip", originalPrice, zhangsan); } public static int calculatePrice(String discountStrategy, int price, User user) { switch (discountStrategy) { case "NoDiscount": return price; case "Discount8": return (int) (price * 0.8); case "Discount95": return (int) (price * 0.95); case "OnlyVip": { if (user.isVip()) { return (int) (price * 0.7); } else { return price; } } default: throw new IllegalStateException("Illegal Input!"); } } }
The program is very simple. Our calculatePrice method is used to calculate the discount amount of users under different mall marketing strategies. For convenience, I will not consider the loss of accuracy in the amount operation in the program.
The biggest problem with this program is that if our mall has a new promotion strategy, such as a 60% discount for the whole game; Tomorrow, when the shopping mall closes down and the whole audience is in tears, a new case needs to be added to the calculatePrice method. If we continue to add dozens of discount schemes in the future, we will constantly modify our business code and make the code lengthy and difficult to maintain.
Therefore, our "strategy" should be separated from the specific business, so as to reduce the coupling between codes and make our codes easy to maintain.
We can use the policy pattern to improve our code.
The DiscountStrategy interface is as follows. Of course, if you also find it, it is a standard functional interface (in order to prevent you from being invisible, I specially added the @ FunctionalInterface annotation ~ doge):
@FunctionalInterface public interface DiscountStrategy { int discount(int price, User user); }
Next, we only need to implement the DiscountStrategy interface for different discount strategies.
NoDiscountStrategy:
public class NoDiscountStrategy implements DiscountStrategy { @Override public int discount(int price, User user) { return price; } }
Discount8Strategy:
public class Discount8Strategy implements DiscountStrategy{ @Override public int discount(int price, User user) { return (int) (price * 0.95); } }
Discount95Strategy:
public class Discount95Strategy implements DiscountStrategy{ @Override public int discount(int price, User user) { return (int) (price * 0.8); } }
OnlyVipDiscountStrategy:
public class OnlyVipDiscountStrategy implements DiscountStrategy { @Override public int discount(int price, User user) { if (user.isVip()) { return (int) (price * 0.7); } else { return price; } } }
In this way, our business code and "discount strategy" are separated:
public class PriceCalculator { public static void main(String[] args) { int originalPrice = 100; User zhangsan = User.vip("Zhang San"); User lisi = User.normal("Li Si"); // No discount calculatePrice(new NoDiscountStrategy(), originalPrice, lisi); // 20% off calculatePrice(new Discount8Strategy(), originalPrice, lisi); // Give a 95% discount calculatePrice(new Discount95Strategy(), originalPrice, lisi); // vip discount calculatePrice(new OnlyVipDiscountStrategy(), originalPrice, zhangsan); } public static int calculatePrice(DiscountStrategy strategy, int price, User user) { return strategy.discount(price, user); } }
After Java 8, a large number of functional interfaces were introduced. We found that the DicountStrategy interface and the BiFunction interface were carved from the same embryo!
DiscountStrategy:
@FunctionalInterface public interface DiscountStrategy { int discount(int price, User user); }
BiFunction:
@FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u); }
Do you feel that you have suffered the loss of not reading API well: -)
After a fierce operation, our code has been reduced to this:
public class PriceCalculator { public static void main(String[] args) { int originalPrice = 100; User zhangsan = User.vip("Zhang San"); User lisi = User.normal("Li Si"); // No discount calculatePrice((price, user) -> price, originalPrice, lisi); // 20% off calculatePrice((price, user) -> (int) (price * 0.8), originalPrice, lisi); // Give a 95% discount calculatePrice((price, user) -> (int) (price * 0.95), originalPrice, lisi); // vip discount calculatePrice( (price, user) -> user.isVip() ? (int) (price * 0.7) : price, originalPrice, zhangsan ); } static int calculatePrice(BiFunction<Integer, User, Integer> strategy, int price, User user) { return strategy.apply(price, user); } }
From the case of discount in this shopping mall, we can see a "mental journey" of Java's support for functional programming. It is worth mentioning that in the code of the last part of this case, I used Lambda expression because the discount strategy is not too complicated, and I mainly want to demonstrate the use of Lambda expression. However, in fact, this is a very bad practice, and I still recommend that you extract different strategies into a class. We see:
(price, user) -> user.isVip() ? (int) (price * 0.7) : price;
This code has begun to become a little complex. If our strategy logic is more complex than this code, even if you write it with Lambda, it will still be difficult for readers to understand (and Lambda is not named, which increases the reader's confusion). Therefore, when you can't use one line of Lambda to complete your function, you should consider extracting this code to prevent affecting the experience of people reading it.
2: Java Stream
Java Stream is one of the most important features of Java 8. Without it, it is the soul of Java functional programming!
There are a lot of online introductions about Java Stream. In this article, I won't introduce too much about the characteristics of Stream and the use of various API s, such as map and reduce (after all, a lot of articles will come out from google). I'm going to explore some new things with you.
As you can see, JDK17 has been published in 2021, but many people's business code is still full of Java 5 syntax - some operations that should be completed with a few lines of Stream code are still replaced by smelly if...else and for loops. Why does this happen? Java 8 is actually an antique. Why not?
That's what I'm going to discuss with you today.
I've summarized two types of people who don't want to use the Stream API:
- The first kind of people said, "I don't know when to use it. Even if I know it can be used, it can't be used well."
- The second kind of people said, "Stream will lead to performance degradation. It's better not to use it."
Then, I will start with these two arguments and analyze with you when to use the Stream, whether the Stream is really so difficult to write, and whether the Stream will affect the performance of the program.
1. When and how can I use Stream?
When can I use Stream? In short, it can be used when the data you operate on is an array or collection (in fact, it is not just an array and collection. In addition to arrays and collections, the source of Stream can also be files, regular expression pattern matchers, pseudo-random number generators, etc., but arrays and collections are the most common).
The reason for the birth of Java Stream is to liberate the productivity of programmers when operating collections. You can compare it to an iterator. Because arrays and collections are iterative, when the data you operate is arrays or collections, you should consider whether you can use Stream to simplify your code.
This kind of consciousness should be subjective. Only when you often operate the Stream will it become more and more handy.
I've prepared a lot of examples. Let's see how Stream frees your hands and simplifies the code: -)
Example 1:
Suppose you have a business requirement to filter out users older than or equal to 60, sort them according to their age, and return their names in the List.
If you do not use Stream, the operation will be as follows:
public List<String> collect(List<User> users) { List<User> userList = new ArrayList<>(); // Filter out users older than or equal to 60 for (User user : users) if (user.age >= 60) userList.add(user); // Sort their ages from big to small userList.sort(Comparator.comparing(User::getAge).reversed()); List<String> result = new ArrayList<>(); for (User user : userList) result.add(user.name); return result; }
If Stream is used, it will be like this:
public List<String> collect(List<User> users) { return users.stream() .filter(user -> user.age >= 60) .sorted(comparing(User::getAge).reversed()) .map(User::getName) .collect(Collectors.toList()); }
What about? Do you feel forced to improve in an instant? And most importantly, the readability of the code is enhanced. You can understand what I'm doing without any comments.
Example 2:
Given a text string and a string array keywords; Judge whether the text contains the keywords in the keyword array. If it contains any keyword, return true; otherwise, return false.
For example:
text = "I am a boy" keywords = ["cat", "boy"]
The result returns true.
If the logic of normal iteration is used, our code is as follows:
public boolean containsKeyword(String text, List<String> keywords) { for (String keyword : keywords) { if (text.contains(keyword)) return true; } return false; }
Then, use Stream as follows:
public boolean containsKeyword(String text, List<String> keywords) { return keywords.stream().anyMatch(text::contains); }
One line of code completes our requirements. Do you think it's a little cool now?
Example 3:
Counts the number of occurrences of all uppercase letters in a given string.
Stream writing method:
public int countUpperCaseLetters(String str) { return (int) str.chars().filter(Character::isUpperCase).count(); }
Example 4:
If you have a business requirement, you need to process the incoming list < employee > as follows: return a mapping from the Department name to all users in the Department, and the users in the same department are sorted from small to large according to their age.
for example
Input is:
[{name=Zhang San, department=Technology Department, age=40 }, {name=Li Si, department=Technology Department, age=30 },{name=Wang Wu, department=Marketing Department, age=40 }]
Output is:
Technology Department -> [{name=Li Si, department=Technology Department, age=30 }, {name=Zhang San, department=Technology Department, age=40 }] Marketing Department -> [{name=Wang Wu, department=Marketing Department, age=40 }]
Stream is written as follows:
public Map<String, List<Employee>> collect(List<Employee> employees) { return employees.stream() .sorted(Comparator.comparing(Employee::getAge)) .collect(Collectors.groupingBy(Employee::getDepartment)); }
Example 5:
Given a Set set of strings, we are required to select all words with length equal to 1, and then connect them with commas.
The code for using Stream operation is as follows:
public String filterThenConcat(Set<String> words) { return words.stream() .filter(word -> word.length() == 1) .collect(Collectors.joining(",")); }
Example 6:
Next, let's look at the problem of LeetCode:
1431. Children with the most candy
I won't describe the problem any more. You can find it yourself
The code of using Stream to solve this problem is as follows:
class Solution { public List<Boolean> kidsWithCandies(int[] candies, int extraCandies) { int max = Arrays.stream(candies).max().getAsInt(); return Arrays.stream(candies) .mapToObj(candy -> (candy + extraCandies) >= max) .collect(Collectors.toList()); } }
So far, I have given you six sample programs. You may find that these small cases are very suitable for the business requirements and logic we usually write, and even if you can't write Stream well, you can seem to understand what these codes are doing.
Of course, I'm not so magical. I can let you understand the world at once (a special technique in ghost killing blade). I just want to tell you that since you can understand it, you can write it well. Stream is a real guy. It can really liberate your hands and improve productivity. Therefore, as long as you know when to use stream and practice it deliberately, these operations are easy.
2. Will stream affect performance?
To be honest, the author doesn't know this problem.
However, my article has been written here. You can't let me delete and rewrite the previous things.
So I Google some articles and read them in detail.
Let's start with the conclusion:
- The performance of Stream is really not as high as that of iterative operation, and the performance of Stream has a great relationship with the running machine. The better the machine performance, the smaller the difference between Stream and for loop. Generally, on 4-core + computers, the difference between Stream and for loop is very small, which is absolutely acceptable
- For basic types, the performance of for loop is better than that of Stream on the whole; For objects, although the performance of Stream is still worse than that of for loop, the gap is not so big compared with that of basic types. This is because the basic type is cache friendly, and the loop itself is JIT friendly, and its natural performance is "much better" than Stream (actually completely acceptable).
- For simple operations, it is recommended to use loops; For complex operations, Stream is recommended. On the one hand, Stream will make your code readable and concise. On the other hand, Java Stream will continue to be upgraded and optimized. Our code can enjoy the benefits of upgrading without any modification.
Test procedure:
Here, my test directly uses the code of the boss in github. Give the code link of the boss:
I won't post the program. You can download the source code of the boss on github by yourself
My test results are as follows
int basic type test:
---array length: 10000000--- minIntFor time:0.026800675 s minIntStream time:0.027718066 s minIntParallelStream time:0.009003748 s ---array length: 100000000--- minIntFor time:0.260386317 s minIntStream time:0.267419711 s minIntParallelStream time:0.078855602 s
Test of String object:
---List length: 10000000--- minStringForLoop time:0.315122729 s minStringStream time:0.45268919 s minStringParallelStream time:0.123185242 s ---List length: 20000000--- minStringForLoop time:0.666359326 s minStringStream time:0.927732888 s minStringParallelStream time:0.247808256 s
As you can see, there is almost no difference between Stream and for loop on the 4-core computer I use. Moreover, multi-core computers can enjoy the benefits of Stream parallel iteration.
3: Summary
Here, the article is finally over. If you can see here, you must be a cruel man.
This paper introduces the concept and idea of functional programming. The article does not involve any mathematical theory and advanced features of functional programming. If you want to understand this part, you can find some information by yourself.
Java Stream is a very important operation. It can not only simplify the code and make your code look clear and easy to understand, but also cultivate your thinking of functional programming.
Well, so far, I have finished this article. Welcome to my official account, kim_talk, here, I hope you can learn more knowledge, and we will see you next time!