The next section deals with Lambda Expressions with are new to Java 8 and fairly mind-bending. A Lambda Expression is a more succinct version of an Anonymous Class.

Anatomy

  • Concise definition of single-method classes.
  • Other names : Closures, function literals, anonymous methods
  • Not a good idea to serialize.
  • Syntax: A -> B
    • A: A comma-separated list of formal parameters enclosed in parentheses or just a single parameter
    • B: A body, which consists of a single expression or a statement block

lambda-expression

  • Like local and anonymous classes, lambda expressions can capture variables but only final and effectively final local variables of the enclosing scope.
  • No shadowing issues as lexically scoped (may only be called (referenced) from within the block of code in which it is defined). Do not inherit any names from a supertype. Do not introduce a new level of scoping.
    public class TopLevel {
      public int x = 0;
      class FirstLevel {
        public int x = 1;
        void methodInFirstLevel(int x) {          
          // y can't be x as the lambda expression is NOT 
          // a new level of scope
          Consumer myConsumer = (y) -> 
            {
              System.out.println(x); // x must remain effectively final
              System.out.println(y); // use parameter to lamdba
              System.out.println(this.x); // use local variable 
              System.out.println(TopLevel.this.x); // use local variable
            };
          myConsumer.accept(x);
     }
    }

Example

The tutorial concentrates on a detailed use case for a social networking app: perform any kind of action on members that satisfy certain criteria, and presents a progression of ways to approach this using an example.

  • Specify Search Criteria Code in a Specific Method for each type of search
    public static void printPersons(List<Person> roster) {
        for (Person p : roster) {
            if (p.getGender() == Person.Sex.MALE) { 
              p.printPerson(); 
        } 
      } 
    }
  • Specify Search Criteria Code in a Class so the search method is more generic
    • Local Class
    • Anonymous Class
  • Specify Search Criteria Code with a Lamba Expression instead of a class 
  • Use A Standard Functional Interface in the Search Method with Lamba Expressions Rather Than A Defined Interface
  • Use Aggregate Operations That Accept Lambda Expressions As Parameters

Specify Search Criteria Code in a Local Class

The search method,printPersons becomes more generic as it takes a CheckPerson parameter which models a particular test. However we now need a Local Class for each type of search.

public static void printPersons(
    List<Person> roster, CheckPerson tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

CheckPerson is actually an Interface so the tester object is an instance of a class that implements a particular type of test method.

interface CheckPerson {
    boolean test(Person p);
}

The main method now contains a definition of a Local Class that implements the test method for the particular type of search. An instance of it is passed to the more generic search method.

printPersons(roster, new CheckPersonEligibleForSelectiveService());

Specify Search Criteria Code in an Anonymous Class

As the search method takes an instance of a class implementing an Interface, we can just define an Anonymous Class on the fly instead of a Local Class.

printPersons(roster,
             new CheckPerson() {
                public boolean test(Person p) {
                    return p.getGender() == Person.Sex.MALE;
                }
             }
        );

Specify Search Criteria Code with a Lamba Expression

The CheckPerson Interface is a functional interface because it only contains one abstract method. Because a functional interface only contains one abstract method, the name of the method, i.e. test,  is not required when it is implemented. This means the Anonymous Class can be further simplified into a Lambda Expression.

printPersons(
  roster,
  // Lambda Expression for abstract method test which has a 
  // Person p parameter.
  // Person p parameter -> method body
  (Person p) -> p.getGender() == Person.Sex.MALE   
  // Or
  p -> p.getGender() == Person.Sex.MALE
);

Use A Standard Functional Interface in the Search Method with Lambda Expressions Rather Than A Defined Interface

The CheckPerson Interface is a functional interface because it only contains one abstract method. This means that rather than defining it , a standard functional interface defined in the JDK in java.util.function could be used instead. For example, Predicate<T> (something to be affirmed or denied) is an interface which contains one abstract method,boolean test (T t) and returns a boolean, where T is the type of the input.

The parameterized type Predicate<Person> has a method with the same return type and parameters as the CheckPerson interface so we can drop the interface from the declaration of the printPersons method and use the standard functional interface instead.  Invoke with the same lambda expression as before.

public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}
  • Generic Interface

The interface Predicate<T> is an example of a generic interface. Generic types specify one or more type parameters (T) within angle brackets (<>). When you declare or instantiate a generic type with actual type arguments you have a parameterized type.

  • Using More Standard Functional Interfaces with Lambda Expressions in the Search Method to make it More Flexible
public static <X, Y> void processElements( // 1. Generic Type Parameters
  Iterable<X> source, // 2. A type of List is also a type of Iterable
  Predicate<X> tester, // 3. Predicate object operates on a generic type
  Function <X, Y> mapper, // 4. Apply 1 arg and return a result
  Consumer<Y> block) { // 5. Perform action on 1 arg
  for (X p : source) { // Iterate over the source list
      if (tester.test(p)) { // Test the lambda expression
        // Apply the function and get the result
        Y data = mapper.apply(p); 
        block.accept(data); // Accept the result into the action
      }
  }
}

processElements(
    roster, // The iterable
    p -> p.getGender() == Person.Sex.MALE // The test predicate
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(), // The function to be applied
    // The consumer that will accept the data
    email -> System.out.println(email) 
);
  1. The processElements method has parameters <X, Y> which are Generic Types.
  2. List<Person> is replaced by an Iterable over a collection of objects as the Interface List extends Collection which extends Iterable.
  3. The Predicate object now operates on whatever type X is.
  4. The Function<T,R> standard functional interface contains the method R apply(T t) which takes one argument of type T and returns a type R. The method uses apply to retrieve the data of type Y from the mapper parameter.
  5. Instead of invoking a specific class method like p.printPerson() you can use the Consumer<T> interface which contains one abstract method, void accept(T t) and pass a lambda expression into the method that specifies the action.

 

Use Aggregate Operations That Accept Lambda Expressions As Parameters

Rather than use a method as above, the same result can be achieved by streaming the list objects through a pipeline of aggregate operations with lambda expressions as arguments.

roster
  .stream() // 1. Turn collection into Stream of elements
  .filter(  
      // 2. Process thru a pipeline of aggregate operations on the Stream
      p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
  .map(p -> p.getEmailAddress())
  .forEach(email -> System.out.println(email));
  1. A Collection interface specifies a stream method which turns the collection into a Stream or sequence of elements.
  2. A pipeline is a sequence of Stream operations which in the example are aggregate operations, filter-map-forEach.
Aggregate Operation Action
Stream<E> stream() Return a Stream of objects
Stream<T> filter(Predicate<? super T> predicate) Filter objects that match a Predicate object (with a lambda expression as a parameter)
<R> Stream<R> map(Function<? super T,? extends R> mapper) .mapMap objects to another value, e.g. String, as specified by a Function object
void forEach(Consumer<? super T> action) Perform an action as specified by a Consumer object

 

An Example Using A Lambda Expression in a Gui

        btn.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });

setOnAction takes an EventHandler<ActionEvent> object. EventHandler is an interface with one abstract methodvoid handle(T event). Because it is a functional interface we can specify it using a lambda expression:

btn.setOnAction( event -> System.out.println("Hello World!") );

Target Typing

The compiler uses the target type of the context the lambda expression is in to determine a target type.

In this context the target type will be CheckPerson:

public static void printPersons(List roster, CheckPerson tester)

In this context the target type will be Predicate<Person>:

public void printPersonsWithPredicate(List roster, Predicate tester)

Lambda expressions can only be used in the situations where the compiler can determine a target type:

  • Variable declarations
  • Assignments
  • Return statements
  • Array initializers
  • Method or constructor arguments
  • Lamdba expression bodies
  • Conditional expressions, ?:
  • Cast expressions

 

The exercise notes for this tutorial are here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s