To better use functional programming, it is not enough to just be familiar with its grammatical structure. It must be considered and accepted from the thought and design level. This programming paradigm is different from the object-oriented programming paradigm that most developers are familiar with.
Let's review some of the key points for using functional programming in the following ways:
Multi-use declarative, less command-style
To better use functional programming, you must first increase the level of abstraction of your code. The use of functional programming in accomplishing the same task requires less code than the imperative, largely because the code of the functional style is more abstract and can be expressed in a richer sense.
When we use imperative style code, we are used to quickly get into the details, such as how many loops to use to solve the problem, what actions are required for each cycle. This is an imperative style, it is because of the low degree of abstraction that leads us to consider the way into the trivial details. This sentiment, should the sentence "trees trees". So a bit of experienced developers, will be particularly interested in code encapsulation, the purpose is to hide the trivial details of the low level of abstraction, reduce the noise of the code.
When coding using a functional style, we should first identify the target of this code. For example, a collection is filtered according to a condition, and some action is made on each element in the collection. This is the goal of the code and don't get bogged down in the underlying details of the code. For example, iterate through the collection and judge each other, put the element that meets the requirements into a new collection, and finally return the new collection. A simple way to judge whether you are in the details is to look closely at the words "traversal" or "loop" in your description. If these words are present, it probably means that you are thinking in a way that is too low. This is a habit of thinking that needs to be avoided when it comes to functional programming. So, this may be the meaning of the internal Walker (Internal Iterator) mentioned earlier, which encapsulates the underlying logic of the traversal, freeing up your mind and giving you the opportunity to look at the problems that need to be addressed from a more macroscopic, more abstract perspective.
For example, when we need to get the highest price in a set of prices, 99.9% of Java developers will do this:
int Max = 0 ; Span class= "K" style= "Font-weight:bold" >for ( int price : prices) {if (max < price) max = Price;}
above is the most typical form of imperative thinking, which relies heavily on looping and traversing such grammatical structures. But think about it, looping and traversing is just a grammatical phenomenon, and it has no direct relationship with the actual problem we are trying to solve. Why do we not look at this problem from a higher perspective? The input parameter is a collection, and the output parameter is an element. This is the typical protocol operation (Reduction operation), which has been provided by the JDK and we only need to use it:
final int max = prices Stream () Reduce (0 , math : max);
The code is a lot more concise, and there is no extra "wheel" to create any "wheels," and the basic methods that already exist are used. For example, the reduce method and the Math.max method reference. This is another attractive part of the functional style: reusability.
Favor invariance (immutability)
For concurrent programming, variable variability can be said to be the source of all evils. When multiple threads modify a variable at the same time, due to a possible race condition (Race Condition), there are many cases where the wrong result is obtained. This also explains from another perspective why it is very difficult to implement efficient and correct concurrency code in imperative code. To deal with too much detail, a variety of small traps and too low-level implementation of the way, can not be difficult?
When you use functional programming, you avoid creating too many mutable variables in nature. The execution of the program does not alter the state of the object, but rather creates a completely new object based on the logic of the input object. This is why functional code is more likely to be parallelized, stemming from the root cause of disruptive parallelism-mutable variables (Mutable Variable).
Therefore, when you find that mutable variables are defined in your code, consider whether there is a way to refactor them to avoid them.
Reduce side effects (Side effects)
The so-called side effect is that this code has an effect on the state of the program outside it. such as modifying the state of an instance, modifying a global variable, and so on.
The so-called side effects, the most common is the return value of void method, this method is generally by modifying the state of the object to complete the calculation logic. This also means that there are variables in the program. Therefore, the side effects of the method are often inextricably linked to variables. When the side effects of the method are eliminated, it probably means that the number of variables is also reduced. If a method has no side effects, it means that the output will always be the same as long as the input parameters are not changed.
When using lambda expressions, you need to ensure that your code has no side effects. If the code has side effects, it violates the original intent of the lambda expression, after all, Lambda is born for functional programming, and one of the features of functional programming is that the implementation of the function should have no side-effects.
Use expressions (expression) instead of statements (Statement)
Expressions and statements are directives that are used by programs to perform certain operations. But there are some differences between them:
- Statement: An operation was performed, but no value was returned
- Expression: An operation was performed and there is a return value
Therefore, the preference for expressions is to reduce side-effects and variables. Because the statement does not return a value, it still performs certain operations, which are usually modifications to the state of the program, or what is the meaning of the statement?
At the same time, the expression and the statement are different also because the expression can be combined, that is, the previously mentioned function chain (functions Chaining). In functional programming, the function chain is very powerful and has good reusability, each function is like a Lego block, and these bricks can assemble endless combinations.
Designing higher-order functions
Higher-order functions are a significant feature introduced in Java 8, where we can only pass in objects or values as parameters to methods. The introduction of higher-order functions allows us to pass functions as parameters. This can undoubtedly greatly increase the abstraction of the code, while reducing the noise of the code, such as the anonymous inner class used frequently in the past, when its definition conforms to the specification of the function interface, it can be referenced directly using a lambda expression or a more concise method.
This code, for example, emerges in a GUI program before Java 8:
button.addActionListener (New ActionListener() { Public void actionperformed(ActionEvent Event) {Joptionpane.Showmessagedialog (Frame,"You clicked!"); }});
And now we can get it plugged into the wings of a lambda expression:
Button. - > Joptionpane . "You clicked!"));
This not only makes this part of the code concise, but also reduces the import statements needed in the source code. Because the interface that needs to be implemented at this time ActionListener is not to be introduced, ActionEvent can also be introduced automatically by means of type deduction.
The rational design and application of higher-order functions allows us to implement some common design patterns very subtly and concisely, without the need to create a large number of types and interfaces like object-oriented design.
About Performance
Some developers worry that large-scale substitution of imperative code with declarative code based on lambda expressions and method references can have some impact on program performance. But in fact, this worry can be said to be superfluous: In most cases, performance will only become better.
The Java 8 specification provides methods to help the compiler optimize. The comparison with lambda expression is closely related to an instruction called "dynamically optimized bytecode" (Invoke dynamic Optimized bytecode). Combining this instruction allows lambda expressions to execute faster.
For example, here is an imperative program for counting the number of mass in a set:
long Primescount = 0 ; for (long number : numbers) {if (IsPrime (number)) Primescount += 1 ;}
Re-form the imperative to the following declaration:
Final Long = numbers . Stream () -> isprime (number)) . Count ();
When the set is an integer of 1-100000, both the imperative and the declarative run time are about 0.025 seconds. But even with the same time-consuming, we can think of declarative style as better: more concise, no side effects, and easier parallelization due to the use of declarative advantages.
Furthermore, it is a separate task to determine whether or not a prime number is a numeric value for each integral type. Therefore, the above procedure is also very easy to parallelize, simply replace the stream method with Parallelstream:
Final Long = numbers . Parallelstream () -> isprime (number)) . Count ();
At this point, the execution time is reduced to 0.006 seconds! In other words, performance has risen by about 400%.
Using functional coding Styles
Starting with Java 8, Java is also a mixed-paradigm programming language like Scala and other languages. It is not difficult to understand how to implement functional encodings in Java 8 through the use of lambda expressions, method references, and higher-order functions.
The hard part is how to flexibly integrate this new programming paradigm with the existing imperative object-oriented paradigm. This requires you to think carefully about the problem at hand, even subversive thinking. Of course, finding some typical use cases is also a great way to learn.
We can take a gradual approach to writing code. First let it work, then let it become better, become more beautiful. There is no doubt that functional coding makes the code more graceful and poetic.
[Java 8] (11) The principle of using lambda