Java 8 – The Road to Lambda expressions

1

image

Java 8 offers wonderful opportunities and new interesting intellectual challenges for Java developers. With this upcoming release, we are once again focused on programming itself – instead of yet another framework – and learning new programming concepts. The introduction of Generics was the last time Java programming was significantly changed – quite some time ago – and this time this change is both more fundamental and more rewarding. Lambda expressions allow for new, compact, elegant ways of programming that bring the notion of Inversion of Control to the core Java programming language. The language itself as well as APIs created with it can make the distinction between the what [should be done]and the how [should be it done]. The what is expressed in a Lambda expression – a function that can be passed around as a parameter – and the how is left to the API implementation or indeed the JVM itself. A great example of this distinction is found in the parallel execution supported in the Collection classes. Another good example is the tremendous elegance of code that performs a series of operations (such as filter, extract, aggregate, operate). More on these operations on Collections (and streams created from collections) in a different article.

imageIn this article I want to show some simple – well, simple to understand and use – examples of lambda expressions.

 

The package java.util.function contains a number of important Interface definitions (and multiple sub-interfaces as well):

  • Predicate – lambda expression that evaluates the outcome of a boolean expression through method test() (a Boolean is returned)
  • Consumer – lambda expression that performs an operation using a passed in object through its accept() operation (no result is returned) (note: formerly known as Block)
  • Function – lambda expression that uses an object passed into to produce a resulting object of potentially a different type
  • BiFunction – lambda expression that uses two objects passed into to produce a resulting object of potentially a different type
  • Supplier – lambda expression that produces an object without getting any actual input through its get() operation
  • Combiner – lambda expression that combines two operands to produce a single result.

Function

An important idea with Lamba expressions is the ability to create a function and pass it just like any other parameter. Here is a simple example of a Function that is created locally and is then handed to another method as an input parameter:

package nl.amis.hrm;

import java.time.LocalDate;
import java.time.Month;
import java.util.function.Function;

public class StraightLamba {

    public Integer deriveSomethingFromPerson(Person person, Function<Person, Integer> functionToPerformOnPerson) {
        Integer value = functionToPerformOnPerson.apply(person);
        // do not know here how this value was calculated from person - it is here now, based on functionToPerformOnPerson's result
        System.out.println("Derived in some magical way: " + value + " using " + functionToPerformOnPerson.toString());
        // execute the lamba expression passed in f using p as its input parameter and return whatever its result is
        return value;
    }

    public static void main(String[] args) {
        Person p = new Person("Louise", "Smith", "Dallas", 16000, LocalDate.of(1976, Month.MARCH, 11), Person.Gender.FEMALE);
        StraightLamba sl = new StraightLamba();

        System.out.println("derived from person " + sl.deriveSomethingFromPerson(p, person -> person.getAge()));

        Function<Person, Person> personMorpher = person -> {
            person.setDateOfBirth(person.getDateOfBirth().plusYears(5));
            return person;
        };
        System.out.println("derived from person " + personMorpher.apply(p).getAge());
    }

}

In this code, the method deriveSomethingFromPerson accepts both a Person object and a Function that acts on a Person object to produce an Integer. The method does not know anything about either the Person or the way in which the Function will derive a value from the Person. It only knows that it can execute the apply method on the Function and it will get an Integer value returned if it passes in a Person.

The call to deriveSomethingFromPerson contains an in-line creation of the Function object – as a Lambda expression using the person -> person.getAge() notation. Note: we could have used any valid Java qualifier instead of person.

The next line creates a Function object, showing how an expression with multiple statements can be used in a Function, using the curly braces {} and a return statement since this is after all a Function. Since this Function personMorpher accepts and returns a Person object, we can invoke the apply method on the Function and call getAge() on the result.

The output of all this is shown below:

image

Predicate

The Predicate is used to evaluate an expression that should return a Boolean. Used in a similar way as the Function, it can be seen as a more specialized sibling of the Function:

 

...
    public void doSomethingWithPersonOnCondition(Person p, Predicate<Person> predicate) {
        if (predicate.test(p)) {
            System.out.println("Person "+p+" satisifies predicate condition");
        }
        else {
            System.out.println("Unfortunatley, Person "+p+" does not satisfy predicate condition");
        };
    }

public static void main(String[] args) {
        Person p = new Person("Louise", "Smith", "Dallas", 16000, LocalDate.of(1976, Month.MARCH, 11), Person.Gender.FEMALE);
        StraightLamba sl = new StraightLamba();

        sl.doSomethingWithPersonOnCondition(p, psn -> psn.getAge()<40);
		....
}		

image

Note: The Predicate Interface contains convenience methods and, negate, or and xor to combine multiple predicates into a new one.

Consumer

The Consumer interface contains an accept operation that accepts an object of the specified type. It will execute its expression, using the input for whatever it has to do. It will not produce any output. Multiple Consumers can be chained together using the and method on the interface.

 

...
   public void doSomethingWithPerson(Person p, Consumer<Person> c) {
        c.accept(p);
    }

    public static void main(String[] args) {
        Person p = new Person("Louise", "Smith", "Dallas", 16000, LocalDate.of(1976, Month.MARCH, 11), Person.Gender.FEMALE);
        sl.doSomethingWithPerson(p, louise -> {            
                                                System.out.println("Personal Details: ");
                                                System.out.println(louise);
                                              }
                                );
...

image

Supplier

The Supplier interface contains a get operation that is invoked to have the expression executed and thereby produce and return an instance of the indicated type. This next code fragment shows how the Supplier – a lambda expression that contains the functionality to produce Person objects – is passed to a method that also receives a Consumer as its input; this particular consumer will take a Person as input and do something really useful with it:

 

    public void doSomethingWithToBeSuppliedPerson(Supplier<Person> supplier, Consumer<Person> consumer) {
        consumer.accept(supplier.get());        
    }



    public static void main(String[] args) {

...
        Consumer<Person> peopleProcessor = persona -> {            
                                                        System.out.println("Report on some Personal Details: ");
                                                        System.out.println(persona);
                                                      };
        Supplier<Person> frankenstein = () -> { Person psn = new Person("James", "Sculley", "Monstropolis", 2000
		                                                               , LocalDate.of(1996, Month.NOVEMBER, 11), Person.Gender.MALE);
										        return psn;
											  };

        sl.doSomethingWithToBeSuppliedPerson(frankenstein, peopleProcessor);
...

The output of running this code is shown here:

image

Resources

Robust step by step overview of Lamba expressions (not in the context of collections) http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#approach6

CoreServlets – Tutorial Series for Java 8 – http://www.coreservlets.com/java-8-tutorial/#lambdas-2

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

1 Comment

Leave a Reply