Java 8: Improving Java Code with Lambda Expressions

In my last lambda how to I introduced the basic syntax for lambda expressions and how they solve some common use cases in Java. This how-to builds upon that to show you how lambda expressions can improve your code. Lambdas should provides a means to better support the DRY (Don't Repeat Yourself) principle and make your code simpler and more readable.

A Common Query Use Case

A common use case for programs is to search through a collection of data to find items that match a specific criteria. In the excellent Jump-Starting Lambda presentation at JavaOne 2012, Stuart Marks and Mike Duigou walk though just such a use case. Given a list of people, various criteria are used to make robocalls (automated phone calls) to matching persons. So this tutorial is going to use that basic premise with some slight variations.

In this example, I decided that a phone call does not fully maximize the marketing potential of our robot. So in addition to calls, the robot can send e-mails and snail mail as well. Thus, using three different contact mediums fully maximizes marketing potential and customer satisfaction (or customer annoyance depending upon one's perspective :). Our message needs to get out to three different groups in the United States:

The actual robot that does all this work has not arrived at our place of business yet. So instead of calling, mailing or emailing, a message is printed to the console. The message contains a person's name, age, and then information specific to the target medium (e.g., email address when e-mailing or phone number when calling).

Person Class

Each person in the test list is defined using the Person class with the following properties:

10 public class Person {
11   private String givenName;
12   private String surName;
13   private int age;
14   private Gender gender;
15   private String eMail;
16   private String phone;
17   private String address;
18   

The Person class uses a Builder to create new objects. A sample list of people is created with the createShortList method. Here is a short code fragment of that method. Note: All source code for this tutorial is included in a NetBeans project which is linked at the end of this page.

128   public static List<Person> createShortList(){
129     List<Person> people = new ArrayList<>();
130     
131     people.add(
132       new Person.Builder()
133             .givenName("Bob")
134             .surName("Baker")
135             .age(21)
136             .gender(Gender.MALE)
137             .email("bob.baker@example.com")
138             .phoneNumber("201-121-4678")
139             .address("44 4th St, Smallville, KS 12333")
140             .build() 
141       );
142     
143     people.add(
144       new Person.Builder()
145             .givenName("Jane")
146             .surName("Doe")
147             .age(25)
148             .gender(Gender.FEMALE)
149             .email("jane.doe@example.com")
150             .phoneNumber("202-123-4678")
151             .address("33 3rd St, Smallville, KS 12333")
152             .build() 
153       );
154     
155     people.add(
156       new Person.Builder()
157             .givenName("John")
158             .surName("Doe")
159             .age(25)
160             .gender(Gender.MALE)
161             .email("john.doe@example.com")
162             .phoneNumber("202-123-4678")
163             .address("33 3rd St, Smallville, KS 12333")
164             .build()
165     );
166     

A First Attempt

With a Person class defined along with the search criteria, it is time to write a RoboContact class. My first instinct would be to write something like the following, where a method is defined for each use case.

RoboContactsMethods.java

   1 package com.example.lambda;
   2 
   3 import java.util.List;
   4 
   5 /**
   6  *
   7  * @author MikeW
   8  */
   9 public class RoboContactMethods {
  10   
  11   public void callDrivers(List<Person> pl){
  12     for(Person p:pl){
  13       if (p.getAge() >= 16){
  14         roboCall(p);
  15       }
  16     }
  17   }
  18   
  19   public void emailDraftees(List<Person> pl){
  20     for(Person p:pl){
  21       if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
  22         roboEmail(p);
  23       }
  24     }
  25   }
  26   
  27   public void mailPilots(List<Person> pl){
  28     for(Person p:pl){
  29       if (p.getAge() >= 23 && p.getAge() <= 65){
  30         roboMail(p);
  31       }
  32     }
  33   }
  34   
  35   
  36   public void roboCall(Person p){
  37     System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  38   }
  39   
  40   public void roboEmail(Person p){
  41     System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  42   }
  43   
  44   public void roboMail(Person p){
  45     System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  46   }
  47 
  48 }

You can see from the names, callDrivers, emailDraftees, and mailPilots, the methods describe what kind of behavior is taking place. The search criteria is clear with an appropriate call to each Robo action that takes place. However, there are some negatives with this design:

Refactor the Methods

So how can the class be fixed? The search criteria would seem to be a good place to start. If test conditions can be isolated in separate methods, that would be an improvement.

RoboContactMethods2.java

   1 package com.example.lambda;
   2 
   3 import java.util.List;
   4 
   5 /**
   6  *
   7  * @author MikeW
   8  */
   9 public class RoboContactMethods2 {
  10   
  11   public void callDrivers(List<Person> pl){
  12     for(Person p:pl){
  13       if (isDriver(p)){
  14         roboCall(p);
  15       }
  16     }
  17   }
  18   
  19   public void emailDraftees(List<Person> pl){
  20     for(Person p:pl){
  21       if (isDraftee(p)){
  22         roboEmail(p);
  23       }
  24     }
  25   }
  26   
  27   public void mailPilots(List<Person> pl){
  28     for(Person p:pl){
  29       if (isPilot(p)){
  30         roboMail(p);
  31       }
  32     }
  33   }
  34   
  35   public boolean isDriver(Person p){
  36     return p.getAge() >= 16;
  37   }
  38   
  39   public boolean isDraftee(Person p){
  40     return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
  41   }
  42   
  43   public boolean isPilot(Person p){
  44     return p.getAge() >= 23 && p.getAge() <= 65;
  45   }
  46   
  47   public void roboCall(Person p){
  48     System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  49   }
  50   
  51   public void roboEmail(Person p){
  52     System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  53   }
  54   
  55   public void roboMail(Person p){
  56     System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  57   }
  58 
  59 }

Well that is better. Now the search criteria is encapsulated in a method. The test conditions can be reused and if a change is made, it would flow back throughout the class. But there still is a lot of repeated code and a separate method is still required for each use case. Hmmm, is there a better way to pass the search criteria to the methods?

Using Anonymous Classes

Before Lambda expressions, an anonymous inner classes might do the trick. An interface could be written, call it MyTest.java, that has one test method that returns a boolean (a functional interface). That way, the desired search criteria could be passed when the method is called. So the interface would look like this:

6 public interface MyTest<T> {
7   public boolean test(T t);
8 }

And, the updated robot class would look like this:

RoboContactsAnon.java

 9 public class RoboContactAnon {
10 
11   public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
12     for(Person p:pl){
13       if (aTest.test(p)){
14         roboCall(p);
15       }
16     }
17   }
18 
19   public void emailContacts(List<Person> pl, MyTest<Person> aTest){
20     for(Person p:pl){
21       if (aTest.test(p)){
22         roboEmail(p);
23       }
24     }
25   }
26 
27   public void mailContacts(List<Person> pl, MyTest<Person> aTest){
28     for(Person p:pl){
29       if (aTest.test(p)){
30         roboMail(p);
31       }
32     }
33   }  
34   
35   public void roboCall(Person p){
36     System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
37   }
38   
39   public void roboEmail(Person p){
40     System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
41   }
42   
43   public void roboMail(Person p){
44     System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
45   }
46   
47 }

That is definitely another improvement. Now only three methods are needed to peform robotic operations. However, there is a slight problem with ugliness when the methods are called. Check out the test class used for this class:

RoboCallTest03.java

   1 package com.example.lambda;
   2 
   3 import java.util.List;
   4 
   5 /**
   6  * @author MikeW
   7  */
   8 public class RoboCallTest03 {
   9 
  10   public static void main(String[] args) {
  11     
  12     List<Person> pl = Person.createShortList();
  13     RoboContactAnon robo = new RoboContactAnon();
  14     
  15     System.out.println("\n==== Test 03 ====");
  16     System.out.println("\n=== Calling all Drivers ===");
  17     robo.phoneContacts(pl, 
  18         new MyTest<Person>(){
  19           @Override
  20           public boolean test(Person p){
  21             return p.getAge() >=16;
  22           }
  23         }
  24     );
  25     
  26     System.out.println("\n=== Emailing all Draftees ===");
  27     robo.emailContacts(pl, 
  28         new MyTest<Person>(){
  29           @Override
  30           public boolean test(Person p){
  31             return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
  32           }
  33         }
  34     );
  35     
  36     
  37     System.out.println("\n=== Mail all Pilots ===");
  38     robo.mailContacts(pl, 
  39         new MyTest<Person>(){
  40           @Override
  41           public boolean test(Person p){
  42             return p.getAge() >= 23 && p.getAge() <= 65;
  43           }
  44         }
  45     );
  46     
  47     
  48   }
  49 }

This is a great example of the "vertical" problem in practice. This code is a little difficult to read. In addition, we are back to writing custom search criteria for each use case.

Lambda Expressions Get it Just Right

Lambda expressions can be used to solve all the problems encountered so far. But first a little housekeeping.

java.util.function

In the previous example, the MyTest functional interface was used to pass anonymous classes to methods. It turns out, I did not need to write that interface. Java 8 provides the java.util.function package with a number of standard functional interfaces. In this case, the Predicate interface meets our needs.

3 public interface Predicate<T> {
4   public boolean test(T t);
5 }

The test method takes a generic class and returns a boolean result. Just what is needed to make selections. So here is the final version of the robot class.

RoboContactsLambda.java

   1 package com.example.lambda;
   2 
   3 import java.util.List;
   4 import java.util.function.Predicate;
   5 
   6 /**
   7  *
   8  * @author MikeW
   9  */
  10 public class RoboContactLambda {
  11   public void phoneContacts(List<Person> pl, Predicate<Person> pred){
  12     for(Person p:pl){
  13       if (pred.test(p)){
  14         roboCall(p);
  15       }
  16     }
  17   }
  18 
  19   public void emailContacts(List<Person> pl, Predicate<Person> pred){
  20     for(Person p:pl){
  21       if (pred.test(p)){
  22         roboEmail(p);
  23       }
  24     }
  25   }
  26 
  27   public void mailContacts(List<Person> pl, Predicate<Person> pred){
  28     for(Person p:pl){
  29       if (pred.test(p)){
  30         roboMail(p);
  31       }
  32     }
  33   }  
  34   
  35   public void roboCall(Person p){
  36     System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  37   }
  38   
  39   public void roboEmail(Person p){
  40     System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  41   }
  42   
  43   public void roboMail(Person p){
  44     System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  45   }
  46 
  47 }

With this approach only 3 methods are needed, one for each contact method. The lambda expression passed to the method selects the Person instances that meet the test conditions.

Verical Problem Solved

Using lambda expressions solves the vertical problem and allows the easy reuse of any expression. Take a look at the new test class updated for lambda expressions.

RoboCallTest04.java

   1 package com.example.lambda;
   2 
   3 import java.util.List;
   4 import java.util.function.Predicate;
   5 
   6 /**
   7  *
   8  * @author MikeW
   9  */
  10 public class RoboCallTest04 {
  11   
  12   public static void main(String[] args){ 
  13 
  14     List<Person> pl = Person.createShortList();
  15     RoboContactLambda robo = new RoboContactLambda();
  16     
  17     // Predicates
  18     Predicate<Person> allDrivers = p -> p.getAge() >= 16;
  19     Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
  20     Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;
  21     
  22     System.out.println("\n==== Test 04 ====");
  23     System.out.println("\n=== Calling all Drivers ===");
  24     robo.phoneContacts(pl, allDrivers);
  25     
  26     System.out.println("\n=== Emailing all Draftees ===");
  27     robo.emailContacts(pl, allDraftees);
  28     
  29     System.out.println("\n=== Mail all Pilots ===");
  30     robo.mailContacts(pl, allPilots);
  31     
  32     // Mix and match becomes easy
  33     System.out.println("\n=== Mail all Draftees ===");
  34     robo.mailContacts(pl, allDraftees);  
  35     
  36     System.out.println("\n=== Call all Pilots ===");
  37     robo.phoneContacts(pl, allPilots);    
  38     
  39   }
  40 }

Notice that a Predicate is set up for each group: allDrivers, allDraftees, and allPilots. Any one of these can be passed to one of the contact methods. The code is compact, easy to read and is not repetitive.

Are lambda expressions cool or what? :)

Resources

The NetBeans project and all the course code is included in the following zip file.

RoboCallExample.zip