The first chapter Hello, lambda expression!
Section I.
Java's coding style is facing tremendous changes.
Our daily work will become simpler and more expressive. Java is a new way of programming that has been in other programming languages as early as a decade ago. Once these new features are introduced into Java, we can write more concise, elegant, expressive, and less error code. We can implement a variety of strategies and design patterns with less code.
In this book we will explore functional-style programming with some examples from everyday programming. Before using this new and elegant way to design code, let's look at where it really is.
Changed the way you think.
Imperative style--java language has been providing this way from the very beginning of birth. With this style, we have to tell Java what to do each step, and then watch it proceed in real step. It's a good thing to do, but it's a bit of a beginner. The code looks a bit verbose, and we want the language to be a little bit smarter, and we should just tell it what we want instead of telling it how to do it. Fortunately, now Java can finally help us achieve this desire. Let's take a look at a few examples to see the advantages and differences of this style.
The normal way
Let's start with two familiar examples. This is an ordered way to see if Chicago is a designated city collection-remember, the code listed in this book is just a partial fragment.
Copy Code code as follows:
Boolean found = false;
for (String city:cities) {
if (City.equals ("Chicago")) {
Found = true;
Break
}
}
System.out.println ("Found Chicago?:" + Found);
The command version looks a bit verbose and rudimentary; it's divided into several operative parts. First initialize a Boolean tag called found, and then iterate through each element of the collection; If you find the city we're looking for, set the tag, jump out of the loop, and print out the results of the lookup.
A better way.
After the careful Java programmer has finished reading the code, it will soon come up with a simpler and clearer way, like this:
Copy Code code as follows:
System.out.println ("Found Chicago?:" + Cities.contains ("Chicago"));
This is also an imperative style of writing--contains method directly to help us fix it.
where the actual improvement
There are several advantages to writing this code:
1. No more tinkering with that variable.
2. Encapsulate the iteration to the bottom
3. More Concise Code
4. Clearer and more focused code
5. Less detours, code and business needs to combine more closely
6. Easy to make mistakes
7. Easy to understand and maintain
Let's take a more complicated example.
This example is so simple that the imperative query of whether an element exists in a collection is ubiquitous in Java. Now let's assume that we're going to use imperative programming for more advanced operations, such as parsing files, interacting with databases, invoking Web services, concurrent programming, and so on. Now we can use Java to write more concise and elegant simultaneous error less code, more than this simple scenario.
The old way
Let's look at another example. We define a range of prices and calculate the total price after discounting in different ways.
Copy Code code as follows:
Final list<bigdecimal> prices = Arrays.aslist (
New BigDecimal ("Ten"), New BigDecimal ("a"), New BigDecimal ("17"),
New BigDecimal ("a"), New BigDecimal ("a"), New BigDecimal ("18"),
New BigDecimal ("a"), New BigDecimal ("12"));
Assuming more than 20 dollars to hit 90 percent, we first in the ordinary way to achieve it again.
Copy Code code as follows:
BigDecimal totalofdiscountedprices = Bigdecimal.zero;
for (BigDecimal price:prices) {
if (Price.compareto (bigdecimal.valueof) > 0)
Totalofdiscountedprices =
Totalofdiscountedprices.add (Price.multiply (bigdecimal.valueof (0.9)));
}
SYSTEM.OUT.PRINTLN ("Total of discounted prices:" + totalofdiscountedprices);
This code is very familiar, first use a variable to store the total price, and then go through all the prices, find more than 20 pieces, calculate their discount price, and add to the total price, and then print out the total price after the discount.
The following is the output of the program:
Copy Code code as follows:
Total of Discounted prices:67.5
The results are completely correct, but such code is a bit messy. This is not our fault, we can only write in the way we have. But such code is a bit rudimentary, not only in the form of bigotry, but also in violation of a single responsibility principle. If you're working from home and you have kids who want to be a code farmer, you have to hide your code, and if they see it, they'll sigh with disappointment, "Do you live on this stuff?" ”
There's a better way.
We can do a lot better-and much better. Our code is a bit like a requirement specification. This reduces the gap between business requirements and the code that is implemented, reducing the likelihood that the requirements will be misread.
Instead of letting Java create a variable and then endlessly assigning it to it, we're going to communicate with it from a higher level of abstraction, like this code below.
Copy Code code as follows:
Final BigDecimal totalofdiscountedprices =
Prices.stream ()
. Filter (Price-> Price.compareto (bigdecimal.valueof) > 0)
. Map (Price-> price.multiply (bigdecimal.valueof (0.9)))
. reduce (Bigdecimal.zero, bigdecimal::add);
SYSTEM.OUT.PRINTLN ("Total of discounted prices:" + totalofdiscountedprices);
Read it out loud--filter out prices larger than 20, convert them to discounted prices, and add them up. This piece of code is exactly the same as the process we describe the requirements. Java can also be very convenient to fold the code of a president, according to the point number in front of the method name for line alignment, as above.
The code is very concise, but we use a lot of new things in Java8. First, we call a stream method of the price list. This opens a door with countless convenient iterators behind the door, which we'll continue to discuss later.
We used some special methods, such as filter and map, instead of traversing the entire list directly. These methods are not like those in our previous JDK, they accept an anonymous function--lambda expression--as arguments. (We'll go into the discussion later). We call the reduce () method to calculate the sum of the prices returned by the map () method.
Like the Contains method, the loop body is hidden. However, the map method (and the filter method) is much more complex. It calls the incoming lambda expression to calculate each price in the price list, putting the result in a new set. Finally, we call the reduce method on the new collection to get the final result.
This is the output of the above code:
Copy Code code as follows:
Total of Discounted prices:67.5
Improvement of the place
This is significantly better than the previous implementation:
1. Good structure without confusion
2. No low-level operation
3. Easy to enhance or modify logic
4. Iteration by Method Library
5. High efficiency; cyclic body inert evaluation
6. Ease of parallelism
Now we'll talk about how Java implements this.
Lambda expression to save the world.
Lambda expressions are shortcuts that keep us away from imperative programming headaches. Java provides this new feature, changing our original programming, so that we write the code is not only simple and elegant, error-prone, and more efficient, easy to optimize the improvement and parallelization.
Section two: The best harvest of functional programming
Functional-style code has a higher signal-to-noise ratio; there is less code to write, but each line or expression does more. Functional programming gives us a lot more benefit than imperative programming:
Avoids explicit modifications or assignments to variables, which are often the source of the bug and cause the code to be difficult to parallel. In command-line programming, we constantly assign values to totalofdiscountedprices variables in the loop. In a functional style, the code no longer has an explicit modification operation. The less the variable is modified, the fewer bugs the code will have.
Functional-style code can be easily implemented in parallel. If the calculation is time-consuming, we can easily let the elements of the list execute concurrently. If we want to parallel the imperative code, we have to worry about the problem of concurrent modification of the totalofdiscountedprices variable. In functional programming, we only access this variable when we are fully disposed of, thus eliminating the pitfalls of thread safety.
The code is more expressive. Imperative programming is divided into several steps to explain what to do-create an initialized value, traverse the price, add the discount price to the variable, and so on--and functional words just need to let the map method of the list return a new list that includes the discounted price and then add it.
Functional programming is simpler; the same result is done with less code than the command. Simpler code means less code to write, less to read, and less to maintain--look at the 7th page of "Simplicity is less simple."
Functional code is more intuitive--reading the code is like describing a problem--once we're familiar with the syntax it's easy to read. The map method executes the given function (calculating the discount price) for each element of the collection, and then returns the result set, as shown in the following illustration.
Figure 1--map executes a given function on each element in the collection
With lambda expressions, we can give full play to the power of functional programming in Java. Using functional style, you can write code that is better, simpler, has fewer assignments, and fewer errors.
Supporting object-oriented programming is one of the main advantages of Java. Functional programming and object-oriented programming are not exclusive. The real style change is to go from command line programming to declarative programming. In Java 8, functional and object-oriented objects can be effectively fused together. We can continue to model the domain entities and their states and relationships in OOP style. In addition, we can create a composite function by modeling behavior or state transitions, workflows, and data processing functions.
Section Three: Why Use functional style?
We see the advantages of functional programming, but do you want to use this new style? Is this just a little improvement or is it a change of face? There are a lot of practical questions to answer before you really spend your time on it.
Copy Code code as follows:
Xiaoming asked:
is the code less concise?
Simplicity is less, not chaos, in the final analysis is to be able to effectively express intentions. The benefits it brings are far-reaching.
Writing code is like piling ingredients together, simplicity means blending ingredients into spices. It takes a hard time to write simple code. The code to read is less, and the really useful code is transparent to you. A short code that is difficult to understand or hide details can only be abbreviated rather than concise.
The simplicity of the code actually smells of agile design. The simplicity of the code is less of the red tape. This means that we can make a quick attempt at the idea, and if it does, go ahead and skip it if the effect is not good.
Writing code in Java is not difficult, and the syntax is simple. And we already know the existing libraries and APIs very well. The real difficulty is to use it to develop and maintain enterprise-class applications.
We want to make sure that our colleagues shut down the database connection at the right time, and that they don't have to keep up with the transaction, that they can handle the exception properly, get and release the lock correctly, and so on.
None of these issues is a big deal to look at alone. But if combined with the complexity of the field, the problem becomes tricky, and resources are strained and difficult to maintain.
What happens if these strategies are encapsulated into many small chunks of code that allow them to manage their own constraints? Then we don't have to spend any more time trying to implement the strategy. This is a huge improvement, so let's look at how functional programming is done.
Crazy iterations.
We've been writing all kinds of iterations to work with lists, collections, and maps. Using iterators in Java is more common, but it's too complicated. Not only do they take up several lines of code, they are also difficult to encapsulate.
How do we traverse the set merge and print them? You can use a for loop. How do we filter out some elements from the collection? Or a For loop, but you'll need to add a few additional modifiable variables. After selecting these values, how do you use them to find the final value, such as minimum, maximum, average, and so on? Then you have to recycle and then modify the variables.
Such an iteration is a panacea, and everything is a little, but nothing. Java now specializes in built-in iterators for many operations: for example, only loops, and map operations, filtered values, reduce operations, and many convenient functions such as maximum minimum, average, and so on. In addition, these operations can be well combined, so we can assemble them together to implement the business logic, which is a simple amount of code. And the written code is readable because it is logically consistent with the order in which the problem is described. In the second chapter, the use of the collection, the 19th page will see a few examples of this, this book examples are also everywhere.
Apply Policy
Policies run through the enterprise-wide application. For example, we need to make sure that a certain operation has been properly authenticated, we want to make sure that the transaction executes quickly, and that the correct update log is updated. These tasks usually end up as a common piece of code at the end of the service, just like the following pseudo code:
Copy Code code as follows:
Transaction Transaction = Getfromtransactionfactory ();
... operation to run within the transaction ...
Checkprogressandcommitorrollbacktransaction ();
Updateaudittrail ();
There are two problems with this approach. First, it usually leads to repetitive workloads and increases the cost of maintenance. Second, it is easy to forget the exceptions that might be thrown out of the business code, which could affect the life cycle of the transaction and modify the update of the log. This should be done using a try, finally block, but whenever someone moves the code, we have to reconfirm that the policy is not compromised.
There's another way we can get rid of the factory and put this code in front of it. Instead of getting the transaction object, you pass the executed code to a well maintained function, like this:
Copy Code code as follows:
Runwithintransaction ((Transaction Transaction)-> {
... operation to run within the transaction ...
});
This is a small step for you, but it saves a lot of things. This strategy for checking the status and updating the log is abstracted and encapsulated into the Runwithintransaction method. We send this method a piece of code that needs to be run in the transaction context. We don't have to worry about who forgot to perform this step or not to handle the exception. This function of implementing the policy has already taken care of this.
We will introduce in the fifth chapter if you use lambda expressions to apply such a strategy.
Extending policies
Strategy seems to be everywhere. In addition to applying them, enterprise applications need to extend them. We would like to have some configuration information to add or remove some of the operations, in other words, the core logic of the module can be processed before the implementation. This is common in Java, but it needs to be considered and designed in advance.
Components that need to be expanded usually have one or more interfaces. We need to carefully design the interface and implement the hierarchical structure of the class. This may work well, but it leaves a whole host of interfaces and classes that need to be maintained. Such a design can easily become cumbersome and difficult to maintain, ultimately destroying the expansion of the original intention.
There is also a workaround-functional interfaces, and lambda expressions-that we can use to design scalable policies. Instead of having to create new interfaces or follow the same method name, we can focus more on the business logic that we want to implement, and we'll mention it in the 73-page décor using lambda expressions.
Easily implement concurrency
A large application is approaching the release milestone, and suddenly a serious performance problem emerges. The team quickly positioned the performance bottleneck in a huge module that handles massive amounts of data. Some in the team suggested that the system's performance could be improved if the multi-core advantages were fully exploited. But if the huge module was written in old Java style, the joy of the proposal was soon dashed.
The team quickly realized that it would take a lot of effort to change the behemoth from serial execution to parallel, adding additional complexity and easily threading-related bugs. Isn't there a better way to raise performance?
Is it possible that serial and parallel code is the same, regardless of whether you choose serial or parallel execution, like clicking on a switch to show your thoughts?
It sounds like it's only in Narnia, but it's all going to be true if we develop it all in a functional way. The built-in iterator and functional style will clear the last hurdle to parallelism. The JDK's design allows for a little bit of code churn to be achieved in serial and parallel execution, which we'll be referring to in the 145-page "leap to complete parallelism."
Tell a Story
A lot of things are lost when business requirements turn into code implementations. The more you lose, the higher the likelihood of error and the cost of managing it. If the code seems to be as descriptive as the requirements, it will be easy to read, discussions with the requirements staff are simpler and easier to meet their needs.
For example, you hear the product manager say, "Get all the stock prices, find the price is more than 500 dollars, calculate the sum of the assets can be dividends." Using the new facilities provided by Java, you can write this:
Copy Code code as follows:
Tickers.map (Stockutil::getprice). Filter (Stockutil::p riceIsLessThan500). SUM ()
This conversion process is almost non-destructive, because there is basically nothing to transform. This is a function that works, and in this book you will see more examples, especially the 8th chapter, using lambda expressions to build the program, 137 pages.
Focus on quarantine
In system development, the core business and the fine-grained logic it needs usually need to be isolated. For example, an order processing system wants to use different tax strategies for different sources of trading. Separating the tax and the rest of the processing logic can make code more reusable and scalable.
In object-oriented programming We call this concern isolation, which is usually solved with a policy model. The solution is generally to create some interfaces and implementation classes.
We can do the same with less code. We can also quickly try our own product ideas, without the need to come up with a bunch of code, stagnant. We'll explore further in the 63 page, using lambda expressions for concern isolation, if you create this pattern through lightweight functions and focus on isolation.
Lazy evaluation
When developing enterprise applications, we may interact with Web services, invoke databases, process XML, and so on. There are a lot of things we need to do, but not all the time. Avoiding certain operations or at least delaying some temporarily unwanted operations is one of the easiest ways to improve performance or reduce program startup, response time.
It's just a little thing, but it takes some work to do it in a purely oop way. To delay initialization of some heavyweight objects, we have to deal with various object references, check null pointers, and so on.
However, if you use the new Optinal class and some of the functional-style APIs it provides, the process will be simple and the code clearer, and we'll discuss this in 105-page delay initialization.
Improve testability
The less logical the code is, the less likely it is to be corrected. In general, functional code is easier to modify and more simple to test.
In addition, like the 4th chapter, using lambda expressions for design and chapter 5th resources, lambda expressions can be used as a lightweight mock object to make the exception test clearer and easier to understand. Lambda expressions can also be used as a good test aid tool. Many common test cases can accept and process lambda expressions. This written test case captures the nature of the functionality that requires regression testing. At the same time, the various implementations that need to be tested can be accomplished by passing in different lambda expressions.
JDK's own automated test cases are also a good example of lambda expressions--For more information you can look at the source code in the OpenJDK warehouse. These test programs allow you to see how lambda expressions parameterize key behaviors of test cases, such as building a test program, "Creating a new result container," and then "checking for some parameterized backend conditions."
We've seen that functional programming not only allows us to write high quality code, but also gracefully solves various challenges in the development process. This means that the development process will become faster, simpler, and less error-as long as you follow the guidelines we'll be introducing later.
Section Fourth: Evolution rather than revolution
We don't have to switch to other languages to enjoy the benefits of functional programming; it's just a few ways to use Java. C++,java,c# these languages support both imperative and object-oriented programming. But now they're all starting to get into the embrace of functional programming. We've just seen these two styles of code and discussed the benefits of functional programming. Now let's look at some of its key concepts and examples to help us learn this new style.
The Java language Development team spends a lot of time and effort adding functional programming capabilities to the Java language and JDK. To enjoy the benefits of it, we have to introduce a few new concepts first. We can only improve our code quality by following a few rules:
1. Declaration Type
2. Promotion of Immutability
3. Avoid side effects
4. Use an expression rather than a statement
5. Using higher-order functions to design
Let's take a look at some of these practice guidelines.
Declaration type
The core of our familiarity with imperative programming is variability and command-driven programming. We create variables, and then we constantly modify their values. We also provide detailed instructions to be executed, such as generating an index flag for an iteration, increasing its value, checking whether the loop is over, updating the nth element of the array, and so on. In the past, because of the features of tools and hardware constraints, we can only write code. We also see that in an immutable set, a declarative contains method is easier to use than a command-style one. All the puzzles and low-level operations are implemented in the library function, and we don't have to worry about the details. For the simple point, we should also use declarative programming. Immutability and declarative programming are the essence of functional programming, and now Java has finally turned it into reality.
Promotion of non-variability
Variable code can have many active paths. The more things you change, the easier it is to break the original structure and introduce more errors. The code that has multiple variables modified is difficult to understand and is difficult to parallel. Immutability fundamentally eliminates these worries. Java supports immutability but not mandatory--but we can. We need to change the old habit of modifying object state. We want to use immutable objects as much as possible. When declaring variables, members, and parameters, try to declare them as final, as Joshua Bloch in the phrase "effective Java", "treat objects as immutable." When creating objects, try to create immutable objects, such as String. When you create a collection, you also try to create immutable or unmodified collections, such as the Arrays.aslist () and collections Unmodifiablelist () methods. To avoid variability we can write pure functions-that is, a function without side effects.
Avoid side effects
Suppose you're writing a piece of code to the Internet to grab a stock price and then write it into a shared variable. If we have a lot of price to crawl, we have to perform these time-consuming operations serially. If we want to use multithreading capabilities, we have to deal with the problems of threading and synchronization, to prevent competitive conditions. The final result is that the program's performance is poor, in order to maintain the thread and sleepless. If the side effects are eliminated, we can completely avoid these problems. A function that has no side effects is highly valued for its immutability and does not modify any input or anything else in its scope. This function is readable, less error, and easier to optimize. With no side effects, there is no need to worry about competitive conditions or concurrent modifications. Not only that, we can also easily execute these functions in parallel, and we'll discuss this in 145 pages.
Use an expression preferentially
The statement is a hot potato because it forces a modification. expressions enhance the ability of immutability and function combinations. For example, we first calculate the total price after the discount with the for statement. Such code leads to variability and lengthy code. Using a more expressive declarative version of the map and sum methods, not only does it avoid modifying operations, but it also concatenates functions. When writing code, you should try to use an expression instead of a statement. This makes the code simpler and easier to understand. The code executes along the business logic, as we describe the problem. If demand changes, a concise version is undoubtedly easier to modify.
Using higher order functions for design
Java does not require immutable like Haskell those functional languages, it allows us to modify variables. So Java is not, and never will be, a purely functional programming language. However, we can use higher-order functions in Java for functional programming. High-order functions make reuse a higher level. With higher-order functions, we can easily reuse small, highly cohesive, mature code. In OOP we are used to passing methods to objects, creating new objects in the method, and then returning the object. Higher-order functions do things to a function just as a method does to an object. With higher order functions, we can.
1. Pass function to function
2. Create a new function within a function
3. Return functions within a function
We've seen an example of a function being passed to another function, and we'll see an example of creating functions and returning functions later. Let's take a look at the example of "passing a function to a function" in the first place:
Copy Code code as follows:
Prices.stream ()
. Filter (Price-> Price.compareto (bigdecimal.valueof) > 0). Map (Price-> price.multiply ( Bigdecimal.valueof (0.9))
Erratum discuss
. reduce (Bigdecimal.zero, bigdecimal::add);
In this code we pass the function price-> price.multiply (bigdecimal.valueof (0.9)) to the map function. The function passed is created when the High-order function map is called. Normally, a function has a function body, a function name, a parameter list, and a return value. The function that is created in real time has a list of arguments followed by an arrow (->), then a very short function body. The type of the parameter is inferred by the Java compiler, and the returned type is implicit. This is an anonymous function, it has no name. But we're not calling it an anonymous function, which we call a lambda expression. Anonymous functions are nothing new in Java as arguments, and we often pass anonymous inner classes before. Even if the anonymous class has only one method, we have to walk through the ritual of creating the class and then instantiating it. With lambda expressions we can enjoy the lightweight syntax. Not only that, we have always used to abstract some concepts into various objects, now we can abstract some of the behavior into lambda expressions. It's still a bit of a no-brainer to use this coding style for programming. We have to turn the already ingrained imperative thinking into a functional type. It may be a bit painful at first, but soon you'll get used to it, and as you go deeper, the non functional APIs are gradually left behind. So let's get to the point where we'll look at how Java handles lambda expressions. We always pass objects to methods, and now we can store them and pass them. Let's look at the secret that Java can use as a parameter behind a function.
Section Fifth: Add a little grammar sugar
This can be done with Java's original functionality, but the lambda expression adds a bit of syntax sugar, eliminating some steps to make our work simpler. The code written in this way not only develops faster, but also expresses our thoughts. In the past, many of the interfaces we used had only one method: like runnable, callable and so on. These interfaces can be found everywhere in the JDK library, where they are usually handled using a single function. The original library function, which only requires a single method interface, can now pass the lightweight function, thanks to the syntactic candy provided by the functional interface. A functional interface is an interface that has only one abstract method. Take a look at the interfaces that have only one method, runnable,callable, and so on, that are applicable to this definition. JDK8 has more of these interfaces--function, predicate, comsumer, supplier, etc. (157 pages, appendix 1 has a more detailed list of interfaces). A functional interface can have more than one static method, and the default method, which is implemented in an interface. We can use @functionalinterface annotations to annotate a functional interface. The compiler does not use this annotation, but with it it is possible to identify the type of the interface more clearly. More than that, if we annotate an interface with this annotation, the compiler enforces that it conforms to the rules of the functional interface. If a method receives a functional interface as an argument, the parameters we can pass include:
1. Anonymous inner class, the oldest way
2.lambda expressions, just like we did in the map method.
3. A reference to the method or constructor (we'll talk about that later)
If the parameter of the method is a functional interface, the compiler will gladly accept a lambda expression or a method reference as an argument. If we pass a lambda expression to a method, the compiler first converts the expression into an instance of the corresponding functional interface. This transformation can be more than just generating an inner class. The method of synchronizing the generated instances of this instance corresponds to the abstract method of the function interface of the parameter. For example, the map method receives a function interface function as an argument. When you call the map method, the Java compiler builds it synchronously, as shown in the following illustration.
The parameters of the lambda expression must match the parameters of the interface's abstract method. This generated method returns the result of the lambda expression. If the return type does not directly match the abstract method, this method converts the return value to the appropriate type. We have probably learned how the next lambda expression is passed to the method. Let's take a quick look at what we just said, and then we'll start our lambda-expression exploration journey.
Summarize
This is a whole new area of Java. With higher-order functions, we can now write elegant and fluent functional-style code. This writing code, simple and easy to understand, fewer errors, conducive to maintenance and parallelism. The Java compiler plays its magic, where we can pass in a lambda expression or a method reference where the function interface parameter is received. We can now go into the world of lambda expressions and the JDK libraries we've transformed to feel their fun. In the next chapter, we'll start with the most common set operations in programming, and play the power of lambda expressions.