preface
In this article, I’ll introduce you to the basic concepts of functional programming, how to write code using functional programming ideas, and how to use Java Stream.
This article will not cover any arcane mathematical concepts, theories of functional programming, or higher-order features of functional programming such as Lazy Evaluation, pattern matching, etc. So, please rest assured to eat.
This article may be helpful to the following groups:
- Who can’t tell you what functional programming is
- Who doesn’t know when to use Java Stream
- Java8 has been out for a long time and still can’t write a Stream operator
The code language used in this article is Java, and Python is used in some cases. If you want to learn more about functional programming and Java Stream operations, I recommend that you read as much as you can at the end of this article π
One: functional programming
1. What is functional programming?
Before I introduce you to functional programming, let’s do a little history.
The theoretical basis of functional programming is Alonzo. The lambda calculus developed by Alonzo Church in the 1930s.
The lambda calculus is a mathematical abstraction in nature and a Formal System in mathematical logic. The system is a programming language designed for a supermachine in which the arguments to functions are functions and the return values are functions. Such functions are denoted by the Greek letter Lambda (Ξ»).
At this time, the lambda calculus was just an idea of Alonzo, a calculation model, and was not applied to any hardware system. It wasn’t until the late 1950s that John McCarthy, a professor at MIT, became interested in Alonzo’s work and in 1958 developed an early functional programming language, LISP, which was arguably a real-world implementation of Alonzo’s lambda calculus. Many computer scientists recognize the power of LISP. In 1973, some programmers at MIT’s Artificial Intelligence Lab developed a machine called the LISP machine, when Alonzo’s Ξ» calculus finally had its own hardware implementation!
So, what is functional programming?
Functional Programming is defined in Wikipedia as follows:
Functional programming is a programming paradigm. It treats the computation as the evaluation of a mathematical function, avoiding changing state and using mutable data. It is a declarative programming paradigm, programming through expressions and declarations rather than statements.
What is FP (Functional Programming)? Since FP is a Programming paradigm, we have to mention another Programming paradigm corresponding to it — traditional Imperative Programming. Let’s take a look at some code examples to give you an intuitive sense of the differences between functional programming and instruction programming. In addition, I want to tell you that you can use the ideas of functional programming to write code without knowing the esoteric concepts of functional programming π
Case 1: binary tree mirroring
This topic can be found on LeetCode, if you are interested, you can do your own search.
It says something like this: Please complete a function, input a binary tree, and the function outputs its mirror image.
For example, enter:
4 / \ 27 / \ / \ 1 3 6 9Copy the code
Mirror output:
4 / \ 7 2 / \ / 9 6 3 1Copy the code
The code for traditional instruction programming looks like this:
# 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
Copy the code
As you can see, instruction programming is like a set of instructions (a list of actions) that we programmers prescribe for a computer to perform. We need to tell the computer in detail what commands to execute at each step, like this code. We first determine whether the node is empty; A temporary variable is then used to store the left subtree and perform a mirror flip of the left and right subtrees, finally switching left and right. We simply write out the steps that the computer needs to do, and then give the machine to run them. This idea of “machine-oriented programming” is called instruction programming.
Let’s look at functional programming style code again:
# 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))
Copy the code
You may wonder where this code represents functional programming.
Hold your horses. Let me explain it to you. The term function was first used by Leibniz in 1694 to describe the relationship between changes in output values and changes in input values. The Chinese word “function” was translated by Li Shanlan, a mathematician in the Qing Dynasty. In his book Algebra, it was explained that “where this variable contains another variable, this is the function of that variable”. Either way, we know that the concept of a function describes a relational mapping, a mapping between one thing and another.
So, we can think functionally, we can get a mirror image of a binary tree. The input of this function is an original tree, and the result is a flipped new tree. The essence of this function is a mapping from the original tree to the new tree.
Further, we can find that the mapping relationship is that each node of the “new tree” is recursively the opposite of the “original tree”.
Although both pieces of code use recursion, they think in very different ways. The former describes “how to get a new tree from the original tree”, while the latter describes the mapping from “the original tree” to “the new tree”.
Case 2: Flipping strings
The problem is getting the flip of a string.
Instruction programming:
def reverse_string(s) :
stack = list(s)
new_string = ""
while len(stack) > 0:
new_string += stack.pop()
return new_string
Copy the code
This Python code is very simple. We simulate loading a string from beginning to end, and then unloading it 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]
Copy the code
How do you understand the idea of functional programming and the logic of writing flipped strings? Get a flip the function of the input string is “string”, after the returned result is flip “new string,” and the nature of this function is from “the original string” to “new string,” a map, will be led by “the original string” split characters and the rest of the rest in turn on before, Putting the first character at the end gives the “new string”, which is the mapping 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 math problems we solved in primary school, which need to be calculated step by step. We care about the process of solving the problem; Functional programming is concerned with the mapping of 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, functions are the basic units, and variables are replaced by functions: in functional programming, variables simply represent expressions, but I still use the term “variable” for better understanding.
Pure functional programming writes functions that have no “variables,” or that 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 deterministic and the output is deterministic, we call this side-effect free. If the state of a function’s internal “variables” is uncertain, it may lead to different outputs from the same input, which is not allowed. Therefore, the “variable” we are talking about here should be unchangeable, and can only be assigned an initial value once.
first class functions
In functional programming, functions are first class objects, and “first class functions” allow your functions to be used as “variables”. A function is an object of the first class, which means that a function can be used as an input parameter value to other functions or as an output of a function, that is, to return a function from a function.
Let’s look at 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
Copy the code
In this example, the inc() function returns another function incx(), so we can use the inc() function to construct various versions of the inc function, such as inc2() and inc5(). This technique is called Currying, and actually uses the “first class functions” of functional programming.
“Natural” support for recursion and tail recursion
The idea of recursion works well with functional programming, a bit like chocolate and music on a rainy day.
Functional programming emphasizes the results of the program rather than the process of execution, as does recursion, where we care more about the return value of recursion, i.e., macro semantics, than about how it is pushed and nested in the computer.
The classic recursive program example is to implement the factorial function, 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)}Copy the code
This code works fine. However, the essence of a recursive program is to call a method, and until the recursion does not reach basecase, the stack will continue to push the stack frame, until the recursive call returns a value, the stack space will be released. If the recursive calls are deep, it can easily lead to performance degradation and even stackOverflowErrors.
Tail recursion is a special kind of recursion. “Tail recursion optimization technology” can avoid the above problems and make it no longer happen stack overflow.
What is tail recursion? A recursive function is said to be tail-recursive if all recursive calls occur at the end of the function.
The factorial code above is not tail-recursive, because we need another step after fact(n-1) is called.
Tail recursion implements the factorial function 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)
}
Copy the code
First of all, tail-recursive optimization requires language or compiler support. Like Java, Python does not have tail-recursive optimization. The reason it does not do tail-recursive optimization is so that it can have a complete Stack Trace output when an exception is thrown. Languages like JavaScript and C are optimized for tail recursion. And the compiler can do this, because when the compiler detected a function call is tail recursion, it will cover the current frame rather than the method of stack into a new pressure, tail recursion by covering the current stack frame, greatly reduce the stack memory used, and the actual operation efficiency has improved significantly.
3. Functional programming of Java8
Functional interface
Java8 introduced the concept of functional interfaces. The goal is to make the Java language support functional programming better.
Here is a functional interface:
public interface Action {
public void action(a);
}
Copy the code
A functional interface can have only one abstract method, but otherwise it looks just like a normal interface.
If you want people to immediately understand that this interface is a FunctionalInterface, you can add the @functionalinterface annotation, which provides no additional functionality beyond qualifying and guaranteeing that your FunctionalInterface has only one abstract method.
@FunctionalInterface
public interface Action {
public void action(a);
}
Copy the code
In fact, there were many functional interfaces long before Java8, such as Runnable, Comparator, InvocationHandler, etc. All of these interfaces fit the definition of functional interfaces.
Common functional interfaces introduced in Java8 include:
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 explaining API usage in articles.
- First point: the JDK documentation has written out the usage of each API in great detail, so there’s no need to go over it again.
- Second point: my article words are limited, in the limited words, the expression should be able to guide readers to think and comment on things, rather than waste everyone’s reading time dross.
So, if you want to understand the use of all functional interfaces, you can consult the documentation.
Lambda expressions and method references
The following code sorts a list by 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) {
returnInteger.compare(s1.length(), s2.length()); }});Copy the code
This code exposes the drawbacks of anonymous classes — verbose and unclear code. In Java8, Lambda expressions were introduced to simplify this form of code. If you use the code compiler IDEA, you will find that after writing this code, the compiler will prompt you: Anonymous New Comparator
() can be replaced with lambda.
When you press Option + Enter, you will find yourself opening 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()));
Copy the code
Next, I’ll give you an incomplete explanation of why Lambda expressions can do this:
In the first place, Lambda expressions can only be used if they have 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’re writing).
Second, Lambda expressions should be written like this without the simplification of type inference:
Collections.sort(words, (String s1, String s2) -> Integer.compare(s1.length(), s2.length()));
Copy the code
It is possible to omit the types in parentheses because Lambda expressions also rely on the type inference mechanism, which allows the compiler to infer the type of the argument table without explicitly naming it if the context information is sufficient. Type inference mechanism is very complicated, you can consult Java8 type inference of this chapter, introduced the JLS, link: * * docs.oracle.com/javase/spec…
After converting the anonymous class into a Lambda expression, your clever friend (actually the clever compiler Doge) discovered it again, and the compiler continued to prompt you: Can be replaced with comparator.paringint.
We continue to press option + Enter and find the Lambda expression simplified to this:
Collections.sort(words, Comparator.comparingInt(String::length));
Copy the code
You find yourself at the door of a new world π
The representation of String::length is called a method reference, which calls the length() method of the String class. Lambda expressions are concise enough, but method references are clearer. When using method references, just use the :: double colon, and both static and instance methods can be referenced in this way.
Item 42,43 of Effective Java, the bible of Java practice, is as follows:
- Prefer lambdas to anonymous classes
- Prefer method references to lambdas
Start with Java8. 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 references are a further refinement of Lambda expressions, which have clearer semantics than Lambda expressions. If method references look shorter and cleaner than Lambda expressions, use method references.
A case study of the policy pattern takes you through Java functional programming again
In this chapter, I have prepared a case of discount in shopping malls for you:
public class PriceCalculator {
public static void main(String[] args) {
int originalPrice = 100;
User zhangsan = User.vip("Zhang");
User lisi = User.normal("Bill");
/ / no discount
calculatePrice("NoDiscount", originalPrice, lisi);
/ / 8 discount
calculatePrice("Discount8", originalPrice, lisi);
/ / 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 {
returnprice; }}default:
throw new IllegalStateException("Illegal Input!"); }}}Copy the code
The program is very simple. Our calculatePrice method is used to calculate the amount of discount for users under different marketing strategies of shopping malls. For the sake of convenience, I will not consider the accuracy loss in the procedure of the amount operation.
The biggest problem with this program is that if our shopping mall has a new promotion strategy, such as a 60% discount for all; Tomorrow, the mall will close down and the whole house will have a tearful sale with a 30% discount, so I need to add a new case to the calculatePrice method. If we continue to add dozens of discount programs, we will have to constantly modify our business code, and make the code long and difficult to maintain.
Therefore, our “policy” should be separated from the specific business in order to reduce the coupling between the code and make our code easier to maintain.
We can use policy patterns to improve our code.
The DiscountStrategy interface is, of course, a standard FunctionalInterface, as you are smart enough to notice (I added the @functionalinterface annotation ~ doge in case you missed it) :
@FunctionalInterface
public interface DiscountStrategy {
int discount(int price, User user);
}
Copy the code
Next, we just need to implement the DiscountStrategy interface for different discount strategies.
NoDiscountStrategy (poor losers don’t deserve discounts) :
public class NoDiscountStrategy implements DiscountStrategy {
@Override
public int discount(int price, User user) {
returnprice; }}Copy the code
Discount8Strategy:
public class Discount8Strategy implements DiscountStrategy{
@Override
public int discount(int price, User user) {
return (int) (price * 0.95); }}Copy the code
Discount95Strategy:
public class Discount95Strategy implements DiscountStrategy{
@Override
public int discount(int price, User user) {
return (int) (price * 0.8); }}Copy the code
OnlyVipDiscountStrategy:
public class OnlyVipDiscountStrategy implements DiscountStrategy {
@Override
public int discount(int price, User user) {
if (user.isVip()) {
return (int) (price * 0.7);
} else {
returnprice; }}}Copy the code
This separates our business code from our “discount strategy” :
public class PriceCalculator {
public static void main(String[] args) {
int originalPrice = 100;
User zhangsan = User.vip("Zhang");
User lisi = User.normal("Bill");
/ / no discount
calculatePrice(new NoDiscountStrategy(), originalPrice, lisi);
/ / 8 discount
calculatePrice(new Discount8Strategy(), originalPrice, lisi);
/ / 95 discount
calculatePrice(new Discount95Strategy(), originalPrice, lisi);
/ / VIP discount
calculatePrice(new OnlyVipDiscountStrategy(), originalPrice, zhangsan);
}
public static int calculatePrice(DiscountStrategy strategy, int price, User user) {
returnstrategy.discount(price, user); }}Copy the code
After Java8, the introduction of a large number of functional interfaces, we found that DicountStrategy interface and BiFunction interface are literally cut from the same embryo!
DiscountStrategy:
@FunctionalInterface
public interface DiscountStrategy {
int discount(int price, User user);
}
Copy the code
BiFunction:
@FunctionalInterface
public interface BiFunction<T.U.R> {
R apply(T t, U u);
}
Copy the code
Do you suddenly feel like you haven’t read the API properly? -)
After some aggressive maneuvering, our code was reduced to something like this:
public class PriceCalculator {
public static void main(String[] args) {
int originalPrice = 100;
User zhangsan = User.vip("Zhang");
User lisi = User.normal("Bill");
/ / no discount
calculatePrice((price, user) -> price, originalPrice, lisi);
/ / 8 discount
calculatePrice((price, user) -> (int) (price * 0.8), originalPrice, lisi);
/ / 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) {
returnstrategy.apply(price, user); }}Copy the code
In this case, we can see the “journey” of Java’s support for functional programming. It is worth mentioning that I used Lambda expressions in the last part of the code in this example, because the discounting strategy is not too complicated, and mainly to demonstrate the use of Lambda expressions. But, in fact, this is a very bad practice, and I still recommend that you extract different policies into one class. We see:
(price, user) -> user.isVip() ? (int) (price * 0.7) : price;
Copy the code
This code is starting to get a little complicated, and if our policy logic is even more complicated than this code, even if you write it in Lambda, it will still be hard for anyone reading it to understand (and Lambda is not named, which adds to the confusion for the reader). So, when you can’t do something with a line of Lambda, consider extracting that code to prevent it from affecting the experience of the person reading it.
2: Java Stream
Java Stream is the single most important feature of Java8. It is the soul of Java functional programming!
There are a lot of articles about Java Stream on the web. In this article, I won’t cover much about Stream features or how to use the various apis, such as: Map, Reduce, etc. (after all, you can Google a bunch of articles yourself), I’m going to explore some of the new stuff with you.
As you can see, JDK17 is out today in 2021, but many people’s business code is still full of Java5 syntax — something that should be done with a Stream of a few lines of code is still done with a smelly long if… Else and for loops instead. Why does this happen? Java8 is actually a dinosaur, why not?
That’s what I want to talk to you about today.
I’ve summarized two types of people who don’t want to use the Stream API:
- The first group says, “I don’t know when to use it. Even if I knew I could use it, I couldn’t use it well.”
- The second group says: “Stream will degrade performance, so it’s better not to use it.”
When should Stream be used? Is Stream really that hard to write? Does it affect application performance?
1. When and how can Stream be used?
When can Stream be used? In short, in a word: This works when the data you’re working with is an array or collection. Stream sources can also be files, regular expression pattern filters, pseudoranrandom number generators, etc. Arrays and collections are the most common.
Java Stream was created to free up programmer productivity when working with collections. You can think of it as an iterator, because arrays and collections are both iterable, so when you’re working with arrays or collections, You should consider whether you can use Stream to simplify your code.
This awareness should be subjective, and you’ll get better at it if you play Stream regularly.
I’ve prepared plenty of examples to see how Stream frees up your hands and simplifies code π
Example 1:
Suppose you have a business requirement to filter out users who are 60 or older, sort them by age and return their names in a List.
The operation without Stream would look like this:
public List<String> collect(List<User> users) {
List<User> userList = new ArrayList<>();
// Select users aged 60 or older
for (User user : users)
if (user.age >= 60)
userList.add(user);
// Rank them from oldest to youngest
userList.sort(Comparator.comparing(User::getAge).reversed());
List<String> result = new ArrayList<>();
for (User user : userList)
result.add(user.name);
return result;
}
Copy the code
If a Stream was used, it would look 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());
}
Copy the code
How’s thatοΌ Do you feel compelled to instantly improve? And most importantly, the code is much more readable, so you can see what I’m doing without any comments.
Example 2:
Given a text string and an array of strings keywords; Check whether the text contains any keyword in the keyword array, return true if any keyword is included, false otherwise.
Such as:
text = "I am a boy"
keywords = ["cat", "boy"]
Copy the code
Returns true.
Using normal iteration logic, our code looks like this:
public boolean containsKeyword(String text, List<String> keywords) {
for (String keyword : keywords) {
if (text.contains(keyword))
return true;
}
return false;
}
Copy the code
Then, using Stream looks like this:
public boolean containsKeyword(String text, List<String> keywords) {
return keywords.stream().anyMatch(text::contains);
}
Copy the code
Doesn’t it feel a little cool now that we’ve done what we need in one line of code?
Example 3:
Count the number of occurrences of all uppercase letters in a given string.
The Stream of writing:
public int countUpperCaseLetters(String str) {
return (int) str.chars().filter(Character::isUpperCase).count();
}
Copy the code
Example 4:
Suppose you have a business requirement to process the incoming List
as follows: return a mapping from the department name to all the users in that department, with the users in the same department sorted in ascending order of age.
For example,
Input for:
[{name= Zhang SAN, department= Technical Department, age=40},{name= Li Si, Department = technical Department, age=30},{name= Wang Wu, department= Marketing, age=40}]Copy the code
The output is:
Technical Department -> [{name= li Si, department= Technical Department, age=30}, {name= Zhang Si, department= technical Department, age=40}] Marketing Department -> [{name= Wang 5, department= Marketing Department, age=40 }]Copy the code
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));
}
Copy the code
Example 5:
Given a Set of strings, we are asked to pick out all words of length equal to 1, and then concatenate them with commas.
The code for the Stream operation is as follows:
public String filterThenConcat(Set<String> words) {
return words.stream()
.filter(word -> word.length() == 1)
.collect(Collectors.joining(","));
}
Copy the code
Example 6:
Let’s look at a problem in LeetCode:
1431. The child with the most candy
I will not describe the problem, we can find a ha ~
Stream = Stream = Stream = Stream
class Solution {
public List<Boolean> kidsWithCandies(int[] candies, int extraCandies) {
int max = Arrays.stream(candies).max().getAsInt();
returnArrays.stream(candies) .mapToObj(candy -> (candy + extraCandies) >= max) .collect(Collectors.toList()); }}Copy the code
So far, I’ve given you six sample programs, and you’ll probably find that these small examples fit well with the business requirements and logic we usually write, and even if you can’t write Stream, you’ll seem to understand what the code is doing.
I’m certainly not magic enough to make you see the world in a flash (a special technique in Blade of Evil). I just want you to know that if you can read it, you can write it. Stream is the real deal, and it really frees up your hands and increases productivity. So, as long as you know when to use Stream and practice it, you can do this.
2. Does Stream affect performance?
To be honest, I don’t know either.
However, my article has been brave enough to write here, you can’t ask me to delete the previous things to rewrite.
So, I googled some articles and read them in detail.
First the conclusion:
- The better the machine, the smaller the difference between Stream and for-loop. Generally, on a 4-core + computer, the difference between Stream and for-loop is very small. Absolutely acceptable
- For-loop performs better overall than Stream for basic types; For objects, Stream performance is still worse than for-loop performance, but it’s not as bad as it would be with primitives. This is because the base type is cache-friendly, and the loop itself is JIT-friendly, naturally performing “much better” than a Stream (actually perfectly acceptable).
- Loops are recommended for simple operations; It is recommended to use Stream for complex operations, partly because it makes your code readable and concise, and partly because Java Stream is constantly being updated and optimized, and our code can take advantage of the upgrade without any changes.
Test procedure:
In this case, I used github code directly for my tests. Give the big guy’s code link:
- IntTest.java
- StringTest.java
Procedures I will not post, you can go to Github to download the big guy’s source code
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.260386317s minIntStream Time: 0.267419711s minIntParallelStream Time: 0.078855602sCopy the code
String tests:
---List length: MinStringForLoop Time :0.315122729 s minStringStream time:0.45268919 s minStringParallelStream 10000000-- minStringForLoop Time :0.315122729 s minStringParallelStream Time: 0.123185242 s - the List length: 20000000-- minStringForLoop Time :0.666359326 s minStringStream time:0.927732888 s minStringParallelStream Time: 0.247808256 sCopy the code
As you can see, there is little difference between Stream and for-loop on my own 4-core computer. And multicore computers can enjoy the benefits of Stream parallel iterations.
Three:
This is the end of the article. If you can see here, you must be one of them.
This paper introduces the concept and idea of functional programming. The article does not cover any mathematical theory and functional programming advanced features, if you want to understand this part of the students, you can find some information.
Java Stream is a very important operation that not only simplifies your code, makes your code look clear and understandable, but also develops your functional programming mindset.
Ok, so far, this article I will introduce the end ~ welcome to pay attention to my public number [kim_talk], here I hope you can harvest more knowledge, we see you next issue!
The resources
- Effective Java
- CoolShell: Functional programming
- What is functional programming thinking
- Dumb functional programming
- How to understand functional programming in Java8
- JLS: Type Inference
- How does Java8 Stream perform?
- Java Stream API performance testing
- Stream Performance