Java functional programming of four: finding elements in a collection

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

Look for the element

Now we are familiar with this elegant method of transforming collections, but it does nothing to find elements. But the filter method is for that.

We're now going to pull out from a list of names names that start with N. Of course, there might be none, and it might turn out to be an empty set. Let's do it the old way.


final List<String> startsWithN = new ArrayList<String>();
for(String name : friends) {
if(name.startsWith("N")) {
startsWithN.add(name);
}
}

That's a lot of code for a simple event like that. We first created a variable and then started it as an empty collection. Then iterate through the original collection, looking for names that begin with the specified letter. If you find it, you insert it into the set.

Let's use the filter method to refactor the above code to see how powerful it is.


final List<String> startsWithN =
friends.stream()
.filter(name -> name.startsWith("N"))
.collect(Collectors.toList());

The filter method receives a lambda expression that returns a Boolean value. If the expression returns true, the element in the run context is added to the result set. If not, skip it. The result is a Steam that contains only those elements whose expression returns true. Finally, we turn this collection into a list with a collect method -- which we'll explore in more depth in the use of collect methods and Collecters classes on page 52.

Let's print out the elements of the result set:


System.out.println(String.format("Found %d names", startsWithN.size()));

It is obvious from the output that this method finds all the matching elements in the collection.

Found 2 names

The filter method, like the map method, also returns an iterator, but that's about it. The collection returned by the map is the same size as the input collection, while the filter returns is not. It returns the size of the set from 0 to the number of elements in the input set. Unlike map, filter returns a subset of the input set.

So far, we've been happy with the simplicity of lambda expressions, but if we're not careful, the problem of code redundancy starts to grow. So let's talk about that.

Reuse of lambda expressions

Lambda expressions look neat, but it's actually easy to get redundant code if you're not careful. Redundancy leads to poor code quality and difficult maintenance; If we want to make a change, we have to change several pieces of the code together.

Avoiding redundancy also helps us improve performance. The relevant code is concentrated in one place, so we can analyze its performance and then optimize the code in this place, which can easily improve the performance of the code.

Now let's look at why using lambda expressions can easily lead to code redundancy, and consider how to avoid it.


final List<String> friends =
Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
final List<String> editors =
Arrays.asList("Brian", "Jackie", "John", "Mike");
final List<String> comrades =
Arrays.asList("Kate", "Ken", "Nick", "Paula", "Zach");
We want to filter out names that start with a certain letter.

We want to filter a name that starts with a letter. Let's do it simply with the filter method.

final long countFriendsStartN =
friends.stream()
.filter(name -> name.startsWith("N")).count();
final long countEditorsStartN =
editors.stream()
.filter(name -> name.startsWith("N")).count();
final long countComradesStartN =
comrades.stream()
.filter(name -> name.startsWith("N")).count();

Lambda expressions make the code look clean, but they also lead to redundancy. In the above example, if we wanted to change a lambda expression, we'd have to change more than one place -- that wouldn't work. Fortunately, we can assign lambda expressions to variables and reuse them, just like we use objects.

Filter method, lambda expressions of the receiver, receiving is a Java. Util. The function. The Predicate references functional interface. Here the Java compiler comes in handy again, generating an implementation of the test method of Predicate with the specified lambda expression. Now we can more explicitly let the Java compiler generate this method instead of regenerating it where the parameters are defined. In the above example, we can explicitly store the lambda expression in a Predicate reference and pass the reference to the filter method. Doing so easily avoids code redundancy.

Let's refactor the previous code to conform to the DRY principle. Don't Repeat Yoursef -- DRY -- principle, see The Pragmatic Programmer: From Journeyman to Master[HT00].


final Predicate<String> startsWithN = name -> name.startsWith("N");
final long countFriendsStartN =
friends.stream()
.filter(startsWithN)
.count();
final long countEditorsStartN =
editors.stream()
.filter(startsWithN)
.count();
final long countComradesStartN =
comrades.stream()
.filter(startsWithN)
.count();

Now instead of repeating that lambda expression, we only wrote it once and stored it in a reference of type Predicate called startsWithN. In the next three filter calls, the Java compiler sees the lambda expression in Predicate disguise, smiling without a word, and silently accepts it.

This newly introduced variable eliminates code redundancy for us. But unfortunately, as we shall see later, the enemy will soon return to avenge himself, and we shall see what better weapons can destroy them for us.


Related articles: