List conversion
Converting a collection into a new collection is as simple as iterating over it. Suppose we want to convert the names in a list to all uppercase. Let’s take a look at some implementation methods.
A string in Java is immutable, so it cannot be changed. We can generate new strings to replace the original elements in the list. However, if you do this, the original list will be gone; there is another problem. The original list may also be immutable, such as generated by Arrays.asList(), so modifying the original list will not work. Another disadvantage is that it is difficult to operate in parallel.
Generating a new all-caps list is a good choice.
At first glance this may sound like weak advice; performance is an issue we all care about. Surprisingly, functional programming is often more performant than imperative programming, as we discuss in Performance Issues on page 153.
Let's start by using this set to generate a new set of uppercase letters.
Copy the code code as follows:
final List<String> uppercaseNames = new ArrayList<String>();
for(String name : friends) {
uppercaseNames.add(name.toUpperCase());
}
In the imperative code, we first create an empty list, then fill it with uppercase names, inserting one at a time while traversing the original list. In order to improve to a functional version, our first step can be to consider replacing the for loop with the internal iterator forEach mentioned in traversing the list on page 19, as shown in the following example.
Copy the code code as follows:
final List<String> uppercaseNames = new ArrayList<String>();
friends.forEach(name -> uppercaseNames.add(name.toUpperCase()));
System.out.println(uppercaseNames);
We use an internal iterator, but we also have to create a new list and insert elements into it. We can improve further.
Using lambda expressions
There is a map method in the newly introduced Stream interface, which can help us stay away from variability and make the code look more concise. Steam is a bit like a collection iterator, but it also provides fluent functions. Using the methods of this interface, we can combine a series of calls so that the code reads like the order describing the problem, making it more readable.
Steam's map method can be used to convert an input sequence into an output sequence - which is a perfect match for what we want to do.
Copy the code code as follows:
friends.stream()
.map(name -> name.toUpperCase())
.forEach(name -> System.out.print(name + " "));
System.out.println();
All collections in JDK8 support this stream method, which encapsulates the collection into a Steam instance. The map method calls the specified lambda expression or code block for each element in the Stream. The map method is very different from the forEach method. forEach simply executes a specified function on the elements in the collection. The map method collects the running results of the lambda expression and returns a result set. Finally we print all the elements using the forEach method.
The names in the new collection are all uppercase:
Copy the code code as follows:
BRIAN NATE NEAL RAJU SARA SCOTT
The map method is very suitable for transforming an input collection into a new output collection. This method ensures that the input and output sequences have the same number of elements. However, the input and output elements can be of different types. In this example, our input and output are both collections of strings. We can pass a piece of code to the map method to have it return, for example, the number of characters contained in the name. In this case, the input is still a sequence of strings, but the output is a sequence of numbers, like the following.
Copy the code code as follows:
friends.stream()
.map(name -> name.length())
.forEach(count -> System.out.print(count + " "));
The result is the number of letters in each name:
Copy the code code as follows:
5 4 4 4 4 5
A later version of the lambda expression is used to avoid explicit modification operations; such code is very concise. Writing this way no longer requires the initialization of an empty collection and the garbage variable; this variable is hidden in the underlying implementation.
Usage method reference
We can also use method references to make it a little more concise. Where an implementation of a functional interface needs to be passed in, the Java compiler can accept lambda expressions or method references. With this feature, you can replace name -> name.toUpperCase() with String::toUpperCase, like this:
Copy the code code as follows:
friends.stream()
.map(String::toUpperCase)
.forEach(name -> System.out.println(name));
When the parameters are passed into the generated method - the implementation of the abstract method of the functional interface - Java will call the toUpperCase method of the String parameter. This parameter reference is hidden here. In a simple scenario like the one above, we can use method references to replace lambda expressions; for more information, see when method references should be used on page 26.
Copy the code code as follows:
A friend asked:
When should you use method references?
When programming in Java, we usually use lambda expressions much more than method references. But this does not mean that method references are unimportant or useless. When lambda expressions are very short, they are a good alternative to directly calling instance methods or static methods. In other words, if the lambda expression only passes parameters to the method call, we should use method references instead.
A lambda expression like this is a bit like what Tom Smykowski said in the movie "Working with a Bug", its job is to "bring requirements from customers to software engineers." Because of this, I call this pattern of refactoring method references the worm pattern.
In addition to being concise, using method references can better reflect the meaning and function of the method name itself.
Behind the use of method references, the compiler plays a key role. The target object and parameters referenced by the method will be derived from the parameters passed in the generated method. This allows you to use method references to write more concise code than using lambda expressions. However, if the parameters are to be modified before being passed to the method or the call result is returned, we cannot use this convenient writing method.
In the previous example, the method reference refers to an instance method. Method references can also refer to a static method and a method that accepts parameters. We will see examples of this later.
Lambda expressions can help us traverse collections and transform collections. As we'll see below, it also helps us quickly select an element from a collection.