Playing with Java 8 Lambda Expressions

September 22nd, 2013 by

Many articles have been written about JSR 335 aka Lambda Expressions for the JavaTM Programming Language but I like to try new things out for myself and that’s why I’d like to share my snippets here.


 

Lambda Hacking using the NetBeans Developer Version

Lambda Hacking using the NetBeans Developer Version

Setup JDK and IDE

It takes just some short steps to setup your environment …

  • Download and install the Java 8 JDK with lambda support
  • Download and install the NetBeans IDE Development version
  • Configure NetBeans to use the Java 8 JDK (> Manage Platforms…)

Examples

First of all the classical user class used for the following examples:

class Person {
 
	String name;
	int age;
 
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
 
	// getter & setter ..
}

Predicates and Filtering

Predicates allow us to define and combine filters that can be applied to a collection. You might know Predicates from Google’s Guava but in Java 8 as Predicate is marked as a @FunctionalInterface a lambda expression can be used to specify the filter.

A lambda expression is defined in the format (param1, param2..) -> { processing instruction} .. the short form with one parameter is param1 -> instruction

Type inference is applied here as you might know it from languages like scala – that’s why in the first predicate the type of “p” is derived from the generic expression.

In the following examples we’re creating some predicates .. one that filters persons with an age greater than 50 years and another for the range that fits for an teenager.

Predicate<Person>
 olderThanFifty = p -> p.getAge() > 50;
Predicate<Person>
 teenager = p -> p.getAge() < 20 && p.getAge() >= 13;
 
Person p1 = new Person("Tim", 13);
Person p2 = new Person("Lisa", 52);
Person p3 = new Person("Bart", 9);
Person p4 = new Person("Mitch", 14);
Person p5 = new Person("Beth", 64);
 
List<Person> persons = new ArrayList<>();
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
persons.add(p5);
 
System.out.println("--- experienced ---");
persons.stream().filter(olderThanFifty).forEach(p -> System.out.println(p.getName()));
 
System.out.println("--- teenagers ---");
List<Person>
 teens = persons.stream().filter(teenager).collect(Collectors.
toList());
teens.forEach(t -> System.out.println(t.getName()));

Output:

--- experienced ---
Lisa
Beth
--- teenagers ---
Tim
Mitch

Collectors

Collectors makes it easy to aggregate the results of a computation into a defined structure. In the following example, we’re filtering all persons for persons with a name beginning with the letter ‘B’, mapping the name from each person from this result and finally using a collector to put them in a generic list of strings.

List<String> namesBeginningWithLetterB = persons.stream().filter(p -> p.getName().startsWith("B")).map(p -> p.getName()).collect(Collectors.toList());
System.out.println("--- names beginning with 'B' ---");
namesBeginningWithLetterB.forEach(name -> {
    System.out.println(name);
});

Output:

--- names beginning with 'B' ---
Bart
Beth

Functional Interfaces, Simplified APIs

@FunctionalInterface is used to indicate that an interface is meant to be used as a functional interface as specified in the JLS – a functional interface has exactly one method.

The interesting part now is, that functional interfaces may be created using a lambda expression. This makes it easy to reduce the amount of boilerplate code in multiple parts of the existing Java API as show in the following example using a Runnable.

In the other example, we’re using a custom functional interface to demonstrate its usage.

System.out.println("--- thread/runnable example ---");
new Thread(() -> System.out.println("I run")).start();
 
@FunctionalInterface
interface Foo {
	void execute(String msg);
}
 
public static void execFoo(Foo foo) {
	foo.execute("test");
}
 
System.out.println("--- @FunctionalInterface example ---");
execFoo((msg) -> System.out.println(msg));

Output:

--- thread/runnable example ---
I run
--- @FunctionalInterface example ---
test

Parallel / Aggregate Example

Using parallelStream creates a possibly parallel stream, in addition there are some new functions to aggregate information from a stream.

System.out.println("--- parallel/aggregates example ---");
long amountTeens = persons.parallelStream().filter(teenager).count();
System.out.println(amountTeens + " teens found");
 
System.out.println("--- additional aggregates example ---");
int min = persons.stream().min((pers1, pers2) -> Integer.compare(pers1.getAge(), pers2.getAge())).get().getAge();
int max = persons.stream().max((pers1, pers2) -> Integer.compare(pers1.getAge(), pers2.getAge())).get().getAge();
System.out.println("min age: " + min + ", max age: " + max);
 
int cumulatedYears = persons.stream().mapToInt(p -> p.getAge()).sum();
System.out.println("all persons have a cumulated age of " + cumulatedYears);
long yearsMultiplied = persons.stream().mapToInt(p -> p.getAge()).reduce(1, (x, y) -> x * y);
System.out.println("years multiplied " + yearsMultiplied);

Output:

--- parallel/aggregates example ---
2 teens found
--- additional aggregates example ---
min age: 9, max age: 64
all persons have a cumulated age of 152
years multiplied 5451264

Sorting

Comparator is now marked with @FunctionalInterface … nuff said ;)

System.out.println("--- sorting example ---");
persons.stream().sorted((pers1, pers2) -> pers1.getName().compareTo(pers2.getName())).forEach(p -> System.out.println("name: " + p.getName() + ", age: " + p.getAge()));

Output:

--- sorting example ---
name: Bart, age: 9
name: Beth, age: 64
name: Lisa, age: 52
name: Mitch, age: 14
name: Tim, age: 13

Optionals / Fallback Values

Not es elegant as in Scala but somehow useful is the handling of unsafe computation results using an Optional.

System.out.println("--- optional examples ---");
static Optional<Person>
 computePossiblyWithoutResult(boolean doesWork) {
	if (doesWork) {
		Person p = new Person("Sam I am", 1);
		return Optional.of(p);
	}
 
	return Optional.empty();
}
 
Optional<Person>
 person = computePossiblyWithoutResult(true);
if (person.isPresent()) {
    System.out.println(person.get().getName());
}
// or better:
person.ifPresent(p -> System.out.println(p.getName()));
 
// with fallback
Person fallbackPerson = new Person("Max", 33);
System.out.println(person.orElse(fallbackPerson).getName());
person = computePossiblyWithoutResult(false);
System.out.println(person.orElse(fallbackPerson).getName());

Output:

--- optional examples ---
Sam I am
Sam I am
Sam I am
Max

Special References

Beware .. a new syntax allows us to reference variables and methods .. in the first example we’re referencing to the list containing a result from a filter operation and use the contains method to filter another list for similar persons.

The second example show a short hand to specify a comparator based on a person’s name.

System.out.println("--- special references #1---");
Predicate<String> namesWithBFilter = namesBeginningWithLetterB::contains;
List<String> otherNames = new ArrayList<>();
otherNames.add("Zed");
otherNames.add("Teddy");
otherNames.add("Beth");
otherNames.add("Jess");
otherNames.stream().filter(namesWithBFilter).forEach(name -> {
    System.out.println(name);
});
 
System.out.println("--- special references #2 ---");
Comparator<Person>
 byNameComparator = Comparator.comparing(Person::getName);
Collections.sort(persons, byNameComparator);
persons.forEach(p -> System.out.println(p.getName()));

Output:

--- special method references #1---
Beth
--- special method references #2 ---
Bart
Beth
Lisa
Mitch
Tim

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/java8-lambda-hacking.git

Resources

Tags: , , , , , , , ,

Search
Categories