This section builds upon the previous examples to show you how lambda expressions can improve your code. Lambdas should provide a means to better support the Don't Repeat Yourself (DRY) 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 robo calls (automated phone calls) to matching persons. This tutorial follows that basic premise with slight variations.

In this example, our message needs to get out to three different groups in the United States:

Drivers: Persons over the age of 16

Persons over the age of 16 Draftees: Male persons between the ages of 18 and 25

Male persons between the ages of 18 and 25 Pilots (specifically commercial pilots): Persons between the ages of 23 and 65

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

Person Class

Each person in the test list is defined by 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 that is linked at the end of this section.

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 and search criteria defined, you can write a RoboContact class. One possible solution defines a method for each use case:

RoboContactsMethods.java

1 package com.example.lambda; 2 3 import java.util.List; 4 5 6 7 @author 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 }

As you can see from the names ( callDrivers , emailDraftees , and mailPilots ) the methods describe the kind of behavior that is taking place. The search criteria is clearly conveyed and an appropriate call is made to each robo action. However, this design has some negatives aspects:

The DRY principle is not followed.

Each method repeats a looping mechanism.



The selection criteria must be rewritten for each method

A large number of methods are required to implement each use case.

The code is inflexible. If the search criteria changed, it would require a number of code changes for an update. Thus, the code is not very maintainable.

Refactor the Methods

How can the class be fixed? The search criteria is a good place to start. If test conditions are 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 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 }

The search criteria are encapsulated in a method, an improvement over the previous example. The test conditions can be reused and changes flow back throughout the class. However there is still a lot of repeated code and a separate method is still required for each use case. Is there a better way to pass the search criteria to the methods?

Anonymous Classes

Before lambda expressions, anonymous inner classes were an option. For example, an interface ( MyTest.java ) written with one test method that returns a boolean (a functional interface) is a possible solution. The search criteria could be passed when the method is called. The interface looks like this:

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

The updated robot class looks 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, because only three methods are needed to perform 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 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 ( "

= = = = Test 03 = = = = " ) ; 16 System.out. println ( "

= = = 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 ( "

= = = 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 ( "

= = = 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 have to write custom search criteria for each use case.

Lambda Expressions Get it Just Right

Lambda expressions solve all the problems encountered so far. But first a little housekeeping.

java.util.function

In the previous example, the MyTest functional interface passed anonymous classes to methods. However, writing that interface was not necessary. Java SE 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. This is just what is needed to make selections. 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 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 three methods are needed, one for each contact method. The lambda expression passed to the method selects the Person instances that meet the test conditions.

Vertical Problem Solved

Lambda expressions solve the vertical problem and allow 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 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 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 ( "

= = = = Test 04 = = = = " ) ; 23 System.out. println ( "

= = = Calling all Drivers = = = " ) ; 24 robo. phoneContacts ( pl, allDrivers ) ; 25 26 System.out. println ( "

= = = Emailing all Draftees = = = " ) ; 27 robo. emailContacts ( pl, allDraftees ) ; 28 29 System.out. println ( "

= = = Mail all Pilots = = = " ) ; 30 robo. mailContacts ( pl, allPilots ) ; 31 32 33 System.out. println ( "

= = = Mail all Draftees = = = " ) ; 34 robo. mailContacts ( pl, allDraftees ) ; 35 36 System.out. println ( "

= = = 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 . You can pass any of these Predicate interfaces to the contact methods. The code is compact and easy to read, and it is not repetitive.

Resources

The NetBeans project with the source code is included in the following zip file.

RoboCallExample.zip