This series of articles is translated from Venkat SubramaniamFunctional Programming in Java
Section 4: evolution rather than revolution
We don't need to switch to other languages to enjoy the benefits of functional programming. All we need to change is to use some Java methods. C ++, Java, and C # all support imperative and object-oriented programming. But now they are all starting to invest in functional programming. We have seen these two types of code and discussed the benefits of functional programming. Now let's take a look at some of its key concepts and examples to help us learn this new style.
The Java Development team spent a lot of time and energy adding functional programming capabilities to the Java language and JDK. To enjoy the benefits it brings, we must first introduce several new concepts. We only need to follow the following rules to improve the quality of our code:
Declarative
Advocating immutability
Avoid side effects
Expressions instead of statements are preferred.
Design with high-level functions
Let's take a look at these practices.
Declarative
The core of imperative programming we are familiar with is variability and command-driven programming. We create variables and constantly modify their values. We also provide detailed instructions to be executed, such as generating iterative index flags, adding its values, checking whether the loop is over, and updating the nth element of the array. In the past, due to tool features and hardware restrictions, we had to write code like this.
We can also see that the declarative contains method is easier to use than the imperative method in an immutable set. All the difficulties and low-level operations are implemented in the library functions, so we don't have to worry about these details. For simplicity, we should also use Declarative Programming. Immutable and declarative programming are the essence of functional programming. Now Java has finally turned it into reality.
Advocating immutability
Variable variable code has many active paths. The more you change, the easier it is to destroy the original structure and introduce more errors. It is difficult to understand and parallelize code with multiple variables modified. Immutable fundamentally eliminates these troubles.
Java supports non-mutable but not mandatory -- but we can. We need to change the old habit of modifying the object state. We should try to use immutable objects.
When declaring variables, members, and parameters, try to declare them as final, just as Joshua Bloch said in the famous saying "Effective Java, "Think of objects as immutable ".
When creating an object, try to create immutable objects, such as String. When creating a set, try to create an unchangeable or unchangeable set, such as using Arrays. asList () and Collections's unmodifiableList () methods.
Without variability, we can write pure functions-that is, functions without side effects.
Avoid side effects
Suppose you are writing a piece of code to capture the price of a stock online and write it into a shared variable. If we have a lot of prices to capture, we have to perform these time-consuming operations serially. If we want to use multi-thread capabilities, we have to deal with the troubles brought by threads and synchronization to prevent competition. The final result is that the program's performance is very poor, and it is useless to maintain the thread. If the side effects are eliminated, we can completely avoid these problems.
A function that has no side effects emphasizes non-variability and does not modify any input or other things within its scope. This function is easy to optimize because it is readable and has fewer errors. Because there are no side effects, there is no need to worry about any competition conditions or concurrent modifications. In addition, we can easily execute these functions in parallel. We will discuss this in page 145.
Use expression first
The statement is a hot potato because it is forced to be modified. Expressions improve the capabilities of non-variability and function combinations. For example, we use the for statement to calculate the total price after the discount. Such Code leads to variability and lengthy code. After a declarative version with more expressive map and sum methods is used, it not only avoids modification operations, but also concatenates functions.
Try to use expressions instead of statements when writing code. This makes the code more concise and easy to understand. The code is executed according to the business logic, just as we described the problem. If the demand changes, the concise version is undoubtedly easier to modify.
Design with high-level functions
Unlike Functional Languages in Haskell, Java enforces immutable requirements. It allows us to modify variables. Therefore, Java is neither a nor a pure functional programming language. However, we can use high-order functions in Java for functional programming.
Higher-order functions bring reuse to the next level. With higher-order functions, we can easily reuse small, specialized, and cohesive mature code.
In OOP, we get used to passing methods to objects, creating new objects in methods, and returning objects. Higher-order functions do the same thing for functions as for objects using methods. With high-level functions, we can
Pass the function to the Function
Create a new function in the function
Returns a function in the function.
We have seen an example of passing a function parameter to another function. We will see examples of creating a function and returning a function later. Let's take a look at the example of "passing a function parameter to a function ".
prices.stream().filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9)))report erratum ? discuss.reduce(BigDecimal.ZERO, BigDecimal::add);
In this Code, we pass the price-> price. multiply (BigDecimal. valueOf (0.9) function to the map function. The passed function is created only when high-order function map is called. Generally, a function has a function body, function name, parameter list, and return value. This real-time function has a parameter list followed by an arrow (->), and then a very short function body. The type of the parameter is derived by the Java compiler, and the returned type is also implicit. This is an anonymous function with no name. But we do not call it an anonymous function. We call it a lambda expression.
Using anonymous functions as parameter passing is nothing new in Java; we often pass anonymous internal classes before. Even if an anonymous class has only one method, we have to go through the class creation ceremony and instantiate it. With lambda expressions, we can enjoy the lightweight syntax. In addition, we used to abstract some concepts into various objects. Now we can abstract some behaviors into lambda expressions.
It still takes some time to design programs with this encoding style. We have to turn the deep-rooted imperative thinking into functional thinking. It may be a bit painful at the beginning, but soon you will get used to it. With the continuous deepening, those non-functional APIs will gradually be left behind.
Let's take a look at how Java handles lambda expressions. We used to pass objects to methods. Now we can store functions and pass them. Let's take a look at the secret behind Java's ability to use functions as parameters.
Section 5: added syntactic sugar
The original functions of Java can also be used to implement these functions. However, lambda expressions add some syntactic sugar, saving some steps and making our work easier. The code written in this way is not only faster, but also express our ideas.
In the past, many interfaces used only one method, such as Runnable and Callable. These interfaces can be seen everywhere in the JDK library. A function is usually used to use them. The original library functions that only require a single method interface can now pass lightweight functions thanks to the syntax sugar provided by the function interface.
Function interfaces are interfaces with only one abstract method. Let's look at the interfaces with only one method, such as Runnable and Callable, which all apply to this definition. JDK8 contains more interfaces such as Function, Predicate, Comsumer, and Supplier (on page 1, Appendix 1 contains a more detailed list of interfaces ). A functional interface can have multiple static methods and default methods, which are implemented in the interface.
You can use the @ FunctionalInterface annotation to mark a function interface. The compiler does not use this annotation, but it can more clearly identify the type of this interface. More than that, if we use this annotation to mark an interface, the compiler will force verify that it complies with the rules of functional interfaces.
If a method receives a function interface as a parameter, the following parameters can be passed:
Anonymous internal class, the oldest way
Lambda expressions, as we do in the map Method
Reference of methods or constructor (we will talk about it later)
If the method parameter is a functional interface, the compiler will be happy to accept lambda expressions or method references as parameters.
If we pass a lambda expression to a method, the compiler will first convert the expression to an instance of the corresponding functional interface. This conversion can be more than generating an internal class. The method generated synchronously corresponds to the abstract method of the Parameter Function interface. For example, the map method receives a functional interface Function as a parameter. When calling the map method, the java compiler will generate it synchronously, as shown in.
The parameters of the lambda expression must match the parameters of the abstract method of the interface. The generated method returns the result of the lambda expression. If the return type does not directly match the abstract method, this method converts the returned value to an appropriate type.
We have probably learned how lambda expressions are passed to methods. Let's take a quick look at what we just talked about and start our exploration of lambda expressions.
Summary
This is a completely new field of Java. Through higher-order functions, we can now write elegant and fluent functional-style code. The code written in this way is concise and easy to understand, with fewer errors, facilitating maintenance and parallelization. The Java compiler uses its magic. When receiving functional interface parameters, we can pass in lambda expressions or method references.
Now we can go to lambda expressions and the world of transformed JDK libraries to feel their pleasure. In the next chapter, we will start from the most common set operations in programming and give full play to the power of lambda expressions.
Finally, the first chapter is boring. The next chapter will start with more practical examples.
Not yet resumed. Follow deepinmind in subsequent articles.
Original article reprinted please indicate the source: http://it.deepinmind.com