Java functional programming of two: the use of collections

  • 2020-04-01 03:31:10
  • OfStack

Chapter 2: the use of collections

We often use collections, Numbers, strings and objects. They are so ubiquitous that even slightly better code for the set of operations makes the code a lot cleaner. In this chapter, we explore how to use lambda expressions to manipulate collections. We use it to iterate over collections, convert collections to new collections, remove elements from collections, and merge collections.

Traverse the list

Traversing a list is a basic collection operation, and its operations have changed over the years. We use a small example of traversing names, from the oldest version to the most elegant version.

We can easily create a list of immutable names with the following code:


final List<String> friends =
Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
System.out.println(friends.get(i));
}

Here's one of the most common, though most general, ways to walk through a list and print it:


for(int i = 0; i < friends.size(); i++) {
System.out.println(friends.get(i));
}

I call this self-masochistic -- it's verbose and error-prone. We need to stop and think. Or i< =?" This only makes sense if we need to manipulate specific elements, but even then we can do it using a functional style that adheres to the immutable principle, which we'll talk about shortly.

Java also provides a relatively advanced for structure.


collections/fpij/Iteration.java
for(String name : friends) {
System.out.println(name);
}

At the bottom, iteration in this way is implemented using the Iterator interface, calling its hasNext and next methods. Both of these approaches are external iterators, which rub together how and what you want to do. We explicitly control the iteration, telling it where to start and where to end; The second version does this at the bottom using the Iterator method. You can also control the iteration with the break and continue statements under explicit operations. The second version is a bit less than the first. It works better than the first one if we're not going to modify an element of the collection. However, both of these approaches are imperative and should be abandoned in today's Java. There are several reasons for the change to functional:

The loop itself is serial and difficult to parallelize.
2. Such a cycle is non-polymorphic; What we get is what we find. Instead of calling a method on the collection (which supports polymorphism) to perform a specific operation, we pass the collection directly to the for loop.
3. At a design level, this code violates the "Tell, Don't Ask" principle. Instead of leaving the iteration to the underlying library, we requested an iteration.

It's time to move from the old imperative programming to the more elegant functional programming of internal iterators. With the internal iterator, we throw a lot of concrete actions to the underlying method library to perform, so you can focus more on specific business requirements. The underlying function will take care of the iteration. Let's first enumerate the list of names with an internal iterator.

The Iterable interface, which is enhanced in JDK8 and has a special name forEach, accepts a parameter of type Comsumer. As the name suggests, the instance of Consumer is passed to its object through its accept method. We use the forEach method using a familiar syntax for anonymous inner classes:


friends.forEach(new Consumer<String>() { public void accept(final String name) {
System.out.println(name); }
});

We call the forEach method on the friends collection, passing it an anonymous implementation of the Consumer. The forEach method calls the incoming Consumer's accept method from each element in the collection to handle the element. In this example we just printed the value, which is the name. Let's take a look at the output of this version, which is the same as the previous two:


Brian
Nate
Neal
Raju
Sara
Scott

We changed only one thing: we dropped the outdated for loop and used the new internal iterator. The advantage is that instead of specifying how to iterate over the set, we can focus more on how to handle each element. The downside is that the code looks more verbose - it takes all the joy out of the new coding style. Fortunately, this is easy to change, which is where lambda expressions and the power of the new compiler come into play. Let's make one more change, replacing the anonymous inner class with a lambda expression.

friends.forEach((final String name) -> System.out.println(name));

That looks a lot better. There's less code, but let's see what that means. The forEach method is a higher-order function that receives a lambda expression or block of code to manipulate the elements in the list. At each invocation, the elements in the collection are bound to the variable name. The underlying cotto handles the work of lambda expression calls. It can determine the execution of delayed expressions and, if appropriate, perform parallel computation. The output of this version is the same as the previous one.
The same code at the page code block index 4
The version of the internal iterator is more concise. Also, using it allows us to focus more on the operation of each element rather than how it's traversed -- this is declarative.

However, this version has drawbacks. Once the forEach method starts executing, unlike the other two versions, we can't get out of this iteration. (there are other ways to do this, of course). As a result, this notation is often used when you need to process every element in a collection. We'll talk about some other functions later that allow us to control the loop.

The standard syntax for lambda expressions is to put parameters inside (), provide type information, and separate them with commas. The Java compiler also does automatic type derivation to free us. It's certainly easier not to type, there's less work, and the world is quiet. Here's what happened after the parameter types were removed in the previous version:


friends.forEach((name) -> System.out.println(name));

In this case, the Java compiler knows from context analysis that the type of name is String. It looks at the signature of the called method forEach, and then analyzes the functional interface in the argument. It then analyzes the abstract methods in the interface to see the number and type of arguments. Even if this lambda expression accepts multiple arguments, we can still do type derivation, but then none of the arguments can be typed; In lambda expressions, the argument types are either not written at all or must be written at all.

The Java compiler gives special treatment to lambda expressions with single arguments: you can omit the parentheses around the arguments if you want to do type derivation.


friends.forEach(name -> System.out.println(name));

There is a small caveat here: the parameters for type derivation are not of final type. In the previous explicitly declared type example, we also marked the parameter as final. This prevents you from changing the value of the argument in a lambda expression. Generally, it is a bad habit to change the value of parameters, which can cause bugs, so marking it as final is a good habit. Unfortunately, if we wanted to use type derivation, we would have to follow the rules and not change the parameters ourselves, because the compiler would no longer protect us.

It took a long time to get to this point, and now the amount of code is actually a little bit less. But that's not even the simplest. Let's try this last minimalist version.


friends.forEach(System.out::println);

In the above code we used a method reference. We can just replace the entire code with the method name. We will explore this in depth in the following section, but let us first recall the quotation of Antoine DE saint-exupery: perfection is not something that cannot be added, but something that cannot be removed.

Lambda expressions allow us to iterate over sets concisely and concisely. In the next section, we'll see how it makes it possible to write such clean code when we're doing deletions and conversions to collections.


Related articles: