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.
|