Sunday 19 October 2014

Java 8 Features

Lambda expressions

Lambda expressions let you express instances of single-method classes more compactly

Injecting Code to a Concrete Functional-Interface

A functional interface is an interface that has only one method signature.

Suppose we have the following CheckPerson single-method, functional interface:

interface CheckPerson { boolean test(Person p); }

and the following printPersons method which uses the above interface:

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

We can use a Lambda expression to invoke the method and define the interface implementation on-the-fly:

printPersons( roster, (Person p) -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );

Limitations

  • Lambda expressions work on a functional interface. This means that the interface should only have one method signature in it.
  • The Lambda expressions and the method in the "functional interface" must evaluate to the same type; If the type evaluated is different between the two, a compile-time-exception occurs.
  • The first braces ( "(Person p)" ) represents the parameters that are passed to the method of the functional-interface. If the method defines the parameter types, that is, does not use generics (see later on), than the "lambda-expression" should also define parameter types.
  • The expression to be evaluated ( "p.getGender() == Person.Sex.MALE [...] && p.getAge() <= 25" ) have two syntax patterns:
    • The first pattern is like the one we used in the above example, without curly braces. The "lambda-expression" is evaluated and the result is the returned value of the functional-interface.
    • The second pattern wraps the lambda-expression in curly-brackets. It is used for more complex lambda-expressions. You have to return the result yourself:

      (Person p) -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; }
    first braces ( "(Person p)" ) represents the parameters that are passed to the method of the functional-interface. If the method defines the parameter types, that is, does not use generics (see later on), than the "lambda-expression" should also define parameter types.

boolean Functional-Interface - The Predicate<T> interface

By using the java.util.function.Predicate interface you can define a Lambda Expression that evaluates to boolean, without creating a concrete interface for it. It also saves the developer from casting to the appropriate type:

public static void printPersonsWithPredicate( List roster, Predicate tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }
printPersonsWithPredicate( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );

void Functional-Interface - The Consumer<T> interface

By using the java.util.function.Consumer interface you can define a Lambda Expression that evaluates to void, without creating a concrete interface for it. It also saves the developer from casting to the appropriate type:

public static void processPersons( List roster, Predicate tester, Consumer block) { for (Person p : roster) { if (tester.test(p)) { block.accept(p); } } }
processPersons( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.printPerson() );

Generic Functional-Interface - The Function<T,R> interface

By using the java.util.function.Function interface you can define a Lambda Expression that evaluates an expression and returns a value, without creating a concrete interface for it. It also saves the developer from casting to the appropriate type:

public static void processPersonsWithFunction( List roster, Predicate tester, Function mapper, Consumer block) { for (Person p : roster) { if (tester.test(p)) { String data = mapper.apply(p); block.accept(data); } } }
processPersonsWithFunction( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) );

Generalizing The Lambda Type

In the previous example we had to define the Generics type. You can further use Genrics so that it can work on any type:

public static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, Function <X, Y> mapper, Consumer<Y> block) { for (X p : source) { if (tester.test(p)) { Y data = mapper.apply(p); block.accept(data); } } }
processElements( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) );

Aggregations with Lambda Expressions

From Java 8 the Collection's interface was added aggregation methods for lambda expressions:

roster .stream() .filter( p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25) .map(p -> p.getEmailAddress()) .forEach(email -> System.out.println(email));

Referencing a Static Method

You can reference a static method by using the double-colon ( "::" ) expression.

For example, if a functional-interface has a method that returns java.util.Locale, you can invoke it by:

( Locale.Category cat ) -> Locale::getDefault(cat)

Referencing an Instance Method

The same way that you reference a static method, you can also reference to an instance mehod. Instead of referring to a type you just refer to an instance variable:

() -> myLocale::getDisplayName()

Referencing a Method of an Arbitrary Instance

When referencing a method of an arbitrary instance with a lambda-expression, you refer to the class-type of the method and the instance method that will be used.

For example:

String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" }; Arrays.sort(stringArray, String::compareToIgnoreCase);

Referencing a Constructor

Referencing a constructor with a lambda-expressions is done with the keyword"new":

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements( SOURCE sourceCollection, Supplier<DEST> collectionFactory) { DEST result = collectionFactory.get(); for (T t : sourceCollection) { result.add(t); } return result; }
Set<Person> rosterSet = transferElements(roster, HashSet::new);

Default Interface Methods

Java version 8 adds the possibility to define default (public) method implementations in an interface. Just add the keyword default to the method's signature:

default void myDefaultMethod() { System.out.println("default method invoked"); }

Static Interface Methods

Java version 8 adds the possibility to define static (public) method implementations in an interface. Just add the keyword static to the method's signature:

static void myDefaultMethod() { System.out.println("default method invoked"); }

Streams API

Java version 8 introduces a new set of APIs that supports aggregate operations on streams of elements. It manipulates data by running operations that are called with functional-style parameters.

The API's top level package is java.util.stream.

The Collections Framework API (interfaces and classes) was added a support to convert collections to streams.