JavaRush /Java Blog /Random EN /Translation of the book. Functional programming in Java. ...
timurnav
Level 21

Translation of the book. Functional programming in Java. Chapter 1

Published in the Random EN group
I will be glad to help you find errors and improve the quality of translation. I translate to improve my English language skills, and if you read and look for translation errors, then you will improve even better than me. The author of the book writes that the book assumes a lot of experience working with Java; to be honest, I myself am not particularly experienced, but I understood the material in the book. The book deals with some theory that is difficult to explain on fingers. If there are decent articles on the wiki, I will provide links to them, but for a better understanding I recommend that you Google it yourself. good luck to everyone. :) For those who want to correct my translation, as well as for those who find it too poor to read in Russian, you can download the original book here . Contents Chapter 1 Hello, Lambda Expressions - currently reading Chapter 2 Using Collections - in development Chapter 3 Strings, Comparators and Filters - in development Chapter 4 Development with Lambda Expressions - in development Chapter 5 Working with Resources - in development Chapter 6 Being Lazy - in development Chapter 7 Optimizing resources - in development Chapter 8 Layout with lambda expressions - in development Chapter 9 Putting it all together - in development

Chapter 1 Hello, Lambda Expressions!

Our Java code is ready for remarkable transformations. The daily tasks we perform become simpler, easier and more expressive. The new way of programming Java has been used for decades in other languages. With these changes to Java, we can write concise, elegant, expressive code with fewer errors. We can use this to easily apply standards and implement common design patterns with fewer lines of code. In this book, we explore the functional style of programming using straightforward examples of problems that we do every day. Before we dive into this elegant style and this new way of developing software, let's see why it's better.
Change your thinking
Imperative style is what Java has given us since the inception of the language. This style suggests that we describe to Java every step of what we want the language to do, and then we simply make sure that those steps are followed faithfully. This worked great, but it's still low level. The code ended up being too verbose, and we often wanted a language that was a little more intelligent. We could then say it declaratively - what we want, and not delve into how to do it. Thanks to the developers, Java can now help us do this. Let's look at a few examples to understand the benefits and differences between these approaches.
The usual way
Let's start with familiar basics to see the two paradigms in action. This uses an imperative method to search for Chicago in the cities collection - the listings in this book only show code snippets. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); The imperative version of the code is noisy (what does this word have to do with it?) and low-level, there are several mutable parts. First we create this stinking boolean flag called found and then we iterate over each element in the collection. If we find the city we are looking for, we set the flag to true and break the loop. Finally we print the result of our search to the console.
There's a better way
As observant Java programmers, a moment's glance at this code can turn it into something more expressive and easier to read, like this: System.out.println("Found chicago?:" + cities.contains("Chicago")); Here's an example of the declarative style - the contains() method helps us get directly to what we need.
Actual changes
These changes will bring a decent amount of improvements to our code:
  • No fuss with mutable variables
  • Loop iterations are hidden under the hood
  • Less code clutter
  • Greater code clarity, focuses attention
  • Less impedance; code closely trails the business intent
  • Less chance of error
  • Easier to understand and support
Beyond simple cases
This was a simple example of a declarative function that checks for the presence of an element in a collection; it has been used for a long time in Java. Now imagine not having to write imperative code for more advanced operations like parsing files, working with databases, making requests for web services, creating multithreading, etc. Java now makes it possible to write concise, elegant code that makes it harder to make mistakes, not just in simple operations, but throughout our entire application.
The old way
Let's look at another example. We are creating a collection with prices and will try several ways to calculate the sum of all discounted prices. Suppose we were asked to sum up all prices whose value exceeds $20, with a 10% discount. Let's first do this in the usual Java way. This code should be very familiar to us: first we create a mutable variable totalOfDiscountedPrices into which we will store the resulting value. We then loop through the price collection, select prices that are above $20, get the discounted price, and add that value to totalOfDiscountedPrices . At the end we display the sum of all prices taking into account the discount. Below is what is output to the console final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
Total of discounted prices: 67.5
It works, but the code looks messy. But it's not our fault, we used what was available. The code is quite low level - it suffers from an obsession with primitives (google it, interesting stuff) and it flies in the face of the single responsibility principle . Those of us who work at home should keep such code away from the eyes of children aspiring to become programmers, it may alarm their fragile minds, be prepared for the question "Is this what you have to do to survive?"
There's a better way, another one
Now we can do better, much better. Our code may resemble a specification requirement. This will help us reduce the gap between business needs and the code that implements them, further reducing the likelihood of requirements being misinterpreted. Instead of creating a variable and then changing it repeatedly, let's work at a higher level of abstraction, such as in the following listing. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Let's read it out loud - price filter is greater than 20, map (create "key" "value" pairs) using the "price" key, price including discount, and then add them
- translator's comment means words that appear in your head while reading the code .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
The code is executed together in the same logical sequence as we have read. The code was shortened, but we used a whole bunch of new things from Java 8. First, we called the stream() method on the prices list . This opens the door to a custom iterator with a rich set of convenience features that we'll discuss later. Instead of directly looping through all the values ​​in the prices list , we use several special methods such as filter() and map() . Unlike the methods we used in Java and the JDK, these methods take an anonymous function - a lambda expression - as a parameter in parentheses. We will study it in more detail later. By calling the reduce() method , we calculate the sum of the values ​​(discounted price) obtained in the map() method . The loop is hidden in the same way as it was when using the contains() method . The filter() and map() methods are however even more complex. For each price in the prices list , they call the passed lambda function and save it to a new collection. The reduce() method is called on this collection to produce the final result. Below is what is output to the console
Total of discounted prices: 67.5
Changes
Below are changes relative to the usual method:
  • The code is pleasing to the eye and not cluttered.
  • No low-level operations
  • Easier to improve or change logic
  • Iteration is controlled by a library of methods
  • Efficient, lazy loop evaluation
  • Easier to parallelize as needed
We'll discuss later how Java provides these improvements.
Lambda to the rescue :)
Lambda is the functional key to freeing us from the hassles of imperative programming. By changing the way we program, with the latest features of Java, we can write code that is not only elegant and concise, but also less error-prone, more efficient, and easier to optimize, improve, and make multi-threaded.
Win Big from Functional Programming
The functional programming style has a higher signal-to-noise ratio ; We write fewer lines of code, but each line or expression performs more functionality. We gained little from the functional version of the code compared to the imperative:
  • We avoided unwanted changes or reassignments of variables, which are a source of errors and make it difficult to process code from different threads at the same time. In the imperative version, we set different values ​​for the totalOfDiscountedPrices variable throughout the loop . In the functional version, there is no explicit change in the variable in the code. Fewer changes lead to fewer bugs in the code.
  • The functional version of the code is easier to parallelize. Even if the calculations in the map() method were lengthy, we can run them in parallel without fear of anything. If we access imperative-style code from different threads, we will need to worry about changing the totalOfDiscountedPrices variable at the same time . In the functional version, we access the variable only after all changes have been made, this frees us from worrying about the thread safety of the code.
  • The code is more expressive. Instead of executing the code in several steps - creating and initializing a variable with a dummy value, looping through the list of prices, adding discount prices to the variable, and so on - we simply ask the list's map() method to return another list of discounted prices and add them up .
  • The functional style is more concise: fewer lines of code are required than the imperative version. More compact code means less to write, less to read, and easier to maintain.
  • The functional version of the code is intuitive and easy to understand, once you know its syntax. The map() method applies the passed function (which calculates the discounted price) to each element of the collection and produces a collection with the result, as we can see in the image below.

Picture Figure 1 - the map method applies the passed function to each element of the collection
With the support of lambda expressions, we can fully harness the power of the functional style of programming in Java. If we master this style, we can create more expressive, more concise code with fewer changes and errors. Previously, one of the key advantages of Java was its support for the object-oriented paradigm. And the functional style does not contradict OOP. Real excellence in moving from imperative to declarative programming. With Java 8 we can combine functional programming with an object-oriented style quite effectively. We can continue to apply the OO style to objects, their scope, state and relationships. In addition, we can model the behavior and state of change, business processes and data processing as a series of function sets.
Why code in functional style?
We've seen the overall benefits of the functional style of programming, but is this new style worth learning? Will this be a minor change in language or will it change our lives? We must get answers to these questions before we waste our time and energy. Writing Java code is not that difficult; the syntax of the language is simple. We are comfortable with familiar libraries and APIs. What really requires us to put in the effort to write and maintain code are the typical Enterprise applications where we use Java for development. We need to make sure that fellow programmers close connections to the database at the correct time, that they do not hold it or perform transactions longer than necessary, that they catch exceptions fully and at the correct level, that they apply and release locks. properly... this sheet can be continued for a very long time. Each of the above arguments alone has no weight, but together, when combined with the inherent implementation complexities, it becomes overwhelming, time-consuming and difficult to implement. What if we could encapsulate these complexities into tiny pieces of code that could also manage them well? Then we would not constantly spend energy on implementing standards. This would give a serious advantage, so let's look at how a functional style can help.
Joe asks
Does a short* code simply mean fewer code letters?
* we are talking about the word concise , which characterizes the functional style of code using lambda expressions
In this context, the code is meant to be concise, without frills, and reduced to direct impact to more effectively convey intent. These are far-reaching benefits. Writing code is like putting ingredients together: making it concise is like adding sauce to it. Sometimes it takes more effort to write such code. Less code to read, but it makes the code more transparent. It is important to keep the code clear when shortening it. Concise code is akin to design tricks. This code requires less dancing with a tambourine. This means that we can quickly implement our ideas and move on if they work and abandon them if they do not live up to expectations.
Iterations on steroids
We use iterators to process lists of objects, as well as to work with Sets and Maps. The iterators that we use in Java are familiar to us; although they are primitive, they are not simple. Not only do they take up a number of lines of code, they are also quite difficult to write. How do we iterate through all the elements of a collection? We could use a for loop. How do we select some elements from the collection? Using the same for loop, but using some additional mutable variables that need to be compared with something from the collection. Then, after selecting a specific value, how do we perform operations on a single value, such as minimum, maximum, or some average value? Again cycles, again new variables. This is reminiscent of the proverb, you can’t see the trees because of the forest (the original uses a play on words related to iterations and means “Everything is taken on, but not everything succeeds” - translator’s note). jdk now provides internal iterators for various statements: one to simplify looping, one to bind the required result dependency, one to filter the output values, one to return the values, and several convenience functions for getting min, max, averages, etc. In addition, the functionality of these operations can be combined very easily, so that we can combine different sets of them to implement business logic with greater ease and less code. When we're done, the code will be easier to understand as it creates a logical solution in the sequence required by the problem. We'll look at some examples of such code in Chapter 2 and later in this book.
Application of algorithms
Algorithms drive enterprise applications. For example, we need to provide an operation that requires authority checking. We will have to make sure that transactions are completed quickly and checks are completed correctly. Such tasks are often reduced to a very ordinary method, as in the listing below: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); There are two problems with this approach. First, this often leads to a doubling of the development effort, which in turn leads to an increase in the cost of maintaining the application. Second, it is very easy to miss exceptions that may be thrown in the code of this application, thus jeopardizing the execution of the transaction and the passage of checks. We can use a proper try-finally block, but every time someone touches this code, we will need to re-check that the logic of the code has not been broken. Otherwise, we could abandon the factory and turn the whole code on its head. Instead of receiving transactions, we could send processing code to a well-managed function, such as the code below. runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); These small changes add up to huge savings. The algorithm for status checking and application checks is given a new level of abstraction and is encapsulated using the runWithinTransaction() method . In this method we place a piece of code that should be executed in the context of a transaction. We no longer have to worry about forgetting to do something or whether we caught the exception in the right place. Algorithmic functions take care of this. This issue will be discussed in more detail in Chapter 5.
Algorithm Extensions
Algorithms are being used more and more often, but in order for them to be fully used in enterprise application development, ways to extend them are required.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION