1. JAVA 8 - BY EXAMPLE!
PATTERNS AND PRACTICES FOR LAMBDAS, STREAMS,
OPTIONAL...
Createdby /MarkHarrison @markglh
2. INTERFACE DEFAULT METHODS
Interfaces can now have default & static methods!
Allows new interface methods to be added without breaking
existing implementations
Multiple inheritance!
public interface Comparator<T> {
default Comparator<T> reversed() {
// Implementation here
}
public static Comparator<T> naturalOrder() {
// Implementation here
}
}
3. MULTIPLE INHERITANCE...
What if a class inherits the same default method from two
interfaces???
1.Class methods always win. Whether it’s this class or the
superclass, it will take priority
2.If multiple interfaces de ne the same method with a
default implementation, then the most speci c is selected
(the child in inheritance terms)
3.If it’s not obvious then the compiler will cry, we need to
explicitly choose: INTERFACE.super.METHODNAME
4. LAMBDAS YOU SAY?
Basically just a shorthand method implementation
Concise and much improved replacement for Anonymous
inner classes
Makes it easy to pass behaviour around
Pass the behaviour into the method, ips the design on its
head
Lexically scoped (this is effectively shared with the
surrounding method)
5. SYNTAX PLEASE!
(int x, int y) -> x + y
Argument types can be inferred by the compiler:
(x, y) -> x + y
Zero-arg Lambdas are also possible:
() -> "Java 8!"
But how can we use them?
Consumer<String> inferredConsumer =
x -> System.out.println(x);
7. FUNCTIONAL INTERFACES
A Functional Interface is any interface with one Single
Abstract Method
The parameters that the Lambda accepts and returns must
match the interface (including exceptions)
@FunctionalInterface annotation is provided to
highlight these interfaces, it is just a marker though, any SAM
interface will work - the annotation simply enforces this at
compile time
So... When we pass or assign a Lambda:
1. First the Lambda is converted into a Functional Interface
2. Secondly it is invoked via this generated implementation
8. LAMBDA EXAMPLE 1 - PREDICATES
Have you ever written code like this?
@FunctionalInterface //Added in Java8
public interface Predicate<T> {
boolean test(T t);
}
private void printMatchingPeople(
List<Person> people,
Predicate<Person> predicate) {
for (Person person : people) {
if (predicate.test(person)) {
System.out.println(person);
}
}
}
9. LAMBDA EXAMPLE 1 - PREDICATES
Pre Java 8:
public class PersonOver50Predicate
implements Predicate<Person> {
public boolean test(Person person) {
return person.getAge() > 50;
}
}
printMatchingPeople(loadsOfPeople,
new PersonOver50Predicate());
10. LAMBDA EXAMPLE 1 - PREDICATES
Java 8:
printMatchingPeople(loadsOfPeople,
x -> x.getAge() > 50);
Notice the signature matches that of the Predicate, the compiler
automatically gures out the rest
What if we had an existing Predicate we wanted to enhance?
Composite Predicates FTW... :-)
Predicate<Person> ageCheck = x -> x.getAge() > 50;
printMatchingPeople(loadsOfPeople,
ageCheck.and(x -> x.getIq() > 100));
11. LAMBDA EXAMPLE 2 - RUNNABLE
Pre Java 8 mess:
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Meh!");
}
};
r1.run();
Java 8:
Runnable r = () -> System.out.println("Woop!");
r.run();
12. LAMBDA EXAMPLE 3 - COLLECTIONS
Lambdas make lots of new Collection methods possible...
List<String> strings = new ArrayList<>();
Collections.addAll(strings, "Java", "7", "FTW");
Do something on every element in the List
strings.forEach(x -> System.out.print(x + " "));
//Prints: "Java 7 FTW"
13. LAMBDA EXAMPLE 4 - MORE COLLECTIONS
Replace every matching element in the List
strings.replaceAll(x -> x == "7" ? "8" : x);
strings.forEach(x -> System.out.print(x + " "));
//Prints: "Java 8 FTW"
Remove matching elements from the List
strings.removeIf(x -> x == "8");
strings.forEach(x -> System.out.print(x + " "));
//Prints: Java FTW
14. LAMBDA EXAMPLE 5 - COMPARATORS
@FunctionalInterface //Added in Java8 version
public interface Comparator<T> {
int compare(T o1, T o2);
//Java 8 adds loads of default methods
}
15. LAMBDA EXAMPLE 6 - COMPARATORS 2
List<Person> loadsOfPeople = ...;
Pre Java 8:
public class SortByPersonAge
implements Comparator<Person> {
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
Collections.sort(loadsOfPeople,
new SortByPersonAge());
Java 8:
Collections.sort(loadsOfPeople,
(p1, p2) -> p1.getAge() - p2.getAge());
16. LAMBDA EXAMPLE 7 - COMPARATORS 3
As usual in Java 8... the Comparator interface provides plenty
of useful default & static methods...
//"comparing" static method simplifies creation
Comparator<Person> newComparator =
Comparator.comparing(e -> e.getIq());
//"thenComparing" combines comparators
Collections.sort(loadsOfPeople,
newComparator.thenComparing(
Comparator.comparing(e -> e.getAge())));
18. INTRODUCING METHOD REFERENCES
Any method can be automatically “lifted” into a function. It
must simply meet contract of the FunctionalInterface
Can be easier to debug & test than Lambdas, more descriptive
stack traces
Promotes re-use, keeping code DRY
Uses the "::" syntax
19. METHOD REFERENCES TYPES
Reference to... Example
a static method Class::staticMethodName
an instance method of
a speci c object
object::instanceMethodName
an instance method of
an arbitrary object
Class::methodName
a constructor ClassName::new
20. REFERENCE TO A STATIC METHOD
A simple reference to a static method
//isPersonOver50 is a static method
printMatchingPeople(loadsOfPeople,
PersonPredicates::isPersonOver50);
This is equivalent to
printMatchingPeople(loadsOfPeople,
x -> x.getAge() > 50);
21. REFERENCE TO AN INSTANCE METHOD OF A
SPECIFIC OBJECT
A reference to a method on an object instance
List<String> strings = ...
//print is a method on the "out" PrintStream object
strings.forEach(System.out::print);
This is equivalent to
strings.forEach(x -> System.out.print(x));
22. REFERENCE TO AN INSTANCE METHOD OF AN
ARBITRARY OBJECT
Examine this simpli ed de nition of a map function
public interface Function<T,R> {
public R apply(T t);
}
public <T, R> List<R> map(Function<T, R> function,
List<T> source) {
/* applies the function to each element,
converting it from T to R */
}
23. REFERENCE TO AN INSTANCE METHOD OF AN
ARBITRARY OBJECT CONT...
Although it looks like we're referencing a Class method, we're
invoking an instance method on the object(s) passed in the call
List<Person> loadsOfPeople = ...
List<Integer> namesOfPeople =
map(Person::getAge, loadsOfPeople);
This is equivalent to
map(person -> person.getAge(), loadsOfPeople);
24. REFERENCE TO A CONSTRUCTOR
Uses the constructor to create new objects, the constructor
signature must match that of the @FunctionalInterface
List<String> digits = Arrays.asList("1", "2", "3");
//Transforms a String into a new Integer
List<Integer> numbers = map(Integer::new, digits);
This is equivalent to
map(s -> new Integer(s), digits);
25. WHAT'S WRONG WITH COLLECTIONS
Every application uses Collections, however Collections are
dif cult to query and aggregate, requiring several levels of
iteration and conditionals, basically it’s messy and painful
Writing multi-threaded code to do this manually is dif cult to
write and maintain
Imagine writing this manually
Stream<String> words=Stream.of("Java", "8", "FTW");
Map<String, Long> letterToNumberOfOccurrences =
words.map(w -> w.split(""))
.flatMap(Arrays::stream)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()));
//Prints: //{a=2, T=1, F=1, v=1, W=1, 8=1, J=1}
26. INTRODUCING STREAMS!
A Stream is a conceptually xed data structure in which
elements are computed on demand
Streams iterate internally, you don’t have to worry about
handling the iteration
Pipelining: Akin to the “pipe” command in unix, allowing
aggregations to be chained together
Automatic optimisation: short-circuiting and lazy processing
Can be in nite
Can be executed in parallel automatically using
parallelStream or parallel()
27. MORE STREAMS...
Can be created from multiple sources:
Arrays.stream(...), Stream.of(1, 2, 3, 4),
Stream.iterate(...), Stream.range(...),
Random.ints(), Files.lines(...)...
Two types of operations:
Intermediate (aggregation): lter, map, atMap, sorted ...
Terminal: collect, reduce, forEach, ndAny ...
Specialised Streams:
IntStream, LongStream and DoubleStream: better
performance for those unboxed types
28. MAP
public <R> Stream<R> map(Function<T, R> mapper);
The mapper Function converts each element from T to R. The
result is then added, as is, to the Stream
29. FLATMAP
public <R> Stream<R> flatMap(Function<T,
Stream<R>> mapper);
The mapper Function converts each element from T to a
Stream of R
This is the key difference to map, the function itself returns a
Stream rather than one element
This Stream is then attened (merged) into the main Stream
To put it another way: atMap lets you replace each value of a
Stream with another Stream, and then it concatenates all the
generated streams into one single stream
30. REDUCE
public T reduce(T identity,
BinaryOperator<T> accumulator);
public Optional<T> reduce(
BinaryOperator<T> accumulator);
//This is the function contained in BinaryOperator
R apply(T t, U u);
Terminal Operation
Takes a Stream of values and repeatedly applies the
accumulator to reduce them into a single value
The accumulator is passed the total so far (T) and the current
element (U)
If passed, identity provides the initial value, rather than the
rst element
31. REDUCE CONTINUED...
int totalAgeUsingReduce = loadsOfPeople.stream()
.map(Person::getAge) //contains 5, 10, 15
.reduce(0, (total, current) -> total + current);
1.First we map the Person to the age int
2.reduce then starts at 0, and adds the current element, 5
3.reduce continues by adding the current total 5, to the next
element, 10
4. nally reduce adds the current total 15, to the next element
15
5.Tada, we've added up all the ages: 30!
32. COLLECTORS
The collect method is a terminal operation which takes various
"recipes", called Collectors for accumulating the elements of
a stream into a summary result, or converting to a speci c type
(such as a List)
List<String> listOfStrings = loadsOfPeople.stream()
.map(x -> x.getName())
.collect(Collectors.toList());
The argument passed to collect is an object of type java
.util.stream.Collector
It describes how to accumulate the elements of a stream into
a nal result
Can be used for Grouping, Partitioning, Averaging, ...
33. STREAMS EXAMPLE 1
List<Integer> numbers = Arrays.asList(1, 2 ... 8);
List<Integer> twoEvenSquares = numbers.stream()
.filter(n -> n % 2 == 0) //Filter odd numbers
.map(n -> n * n) ////Multiply by itself
.limit(2)//Limit to two results
.collect(Collectors.toList()); //Finish!
Imagine a println in each step...
filtering 1
filtering 2
mapping 2
filtering 3
filtering 4
mapping 4
35. STREAMS EXAMPLE 2
List<String> youngerPeopleSortedByIq =
loadsOfPeople.stream()
.filter(x -> x.getAge() < 50)
.sorted(Comparator
.comparing(Person::getIq).reversed())
.map(Person::getName)
.collect(Collectors.toList());
1. Filter out all people older than 50
2. Inverse sort the remaining people by IQ
3. map each person to their name (convert to a Stream<String>)
4. Convert the result to a List
36. STREAMS EXAMPLE 3 - SUM
int combinedAge =
loadsOfPeople.stream()
.mapToInt(Person::getAge) //returns IntStream
.sum(); //this HAS to be a specialised Stream
1. map each person to their age, producing an IntStream
2. sum the results, also supports average
37. STREAMS EXAMPLE 4 - MAP, REDUCE
String xml =
"<people>" +
loadsOfPeople.stream()
.map(x -> "<person>"+ x.getName() +"</person>")
.reduce("", String::concat) //start with ""
+ "</people>";
map each Person to an XML element
(<person>Steve</person>), then use String.concat to reduce
the Stream into one XML String
<people>
<person>Dave</person>
<person>Helen</person>
<person>Laura</person>
<person>Ben</person>
</people>
38. STREAMS EXAMPLE 5 - MAP
List<Stream<Person>> clonedPeople =
loadsOfPeople.stream()
.map(person -> Stream.of(person, person.dolly()))
.collect(Collectors.toList());
1.map creates a new Stream containing two people
2.This Stream is then added to the main Stream as-is, leaving
us with a pretty useless: List<Stream<Person>>
39. STREAMS EXAMPLE 6 - FLATMAP
List<Person> clonedPeople2 = loadsOfPeople.stream()
.flatMap(person -> Stream.of(person,
person.dolly()))
.collect(Collectors.toList());
1. atMap combines each element from the new Streams into
one Stream<Person>
2.So now we've got what we wanted in the rst place, a
List<Person>
Sweeet!
40. STREAMS EXAMPLE 7 - REDUCE
int totalAgeUsingReduce = loadsOfPeople.stream()
.map(Person::getAge)
.reduce((total, current) -> total + current)
.get(); //get the result from the Optional
This is the same as the previous example, the difference being
we don't specify a default value for reduce
Not specifying a default value means the result is Optional...
if the Stream is empty then so is the result!
41. STREAMS EXAMPLE 8 - GROUPING
Map<Integer, List<Person>> peopleGroupedByAge =
loadsOfPeople.stream()
.filter(x -> x.getIq() > 110)
.collect(Collectors.groupingBy(Person::getAge));
The collect method groups the ltered results by age,
producing a Map<age, <Person>>
{52=[Person{... age=52, iq=113, gender=MALE}],
60=[Person{... age=60, iq=120, gender=FEMALE}],
28=[Person{... age=28, iq=190, gender=MALE}]}
42. STREAMS EXAMPLE 9 - PARTITIONING
Map<Boolean, List<Person>> peoplePartitionedByAge =
loadsOfPeople.stream().filter(x -> x.getIq() > 110)
.collect(Collectors
.partitioningBy(x -> x.getAge() > 55));
The collect method partitions the ltered results by age
The Map will have two entries, true and false, according to the
Predicate
{false=[Person{... age=28, iq=190, gender=MALE}],
true=[Person{... age=60, iq=120, gender=FEMALE}]}
43. STREAMS EXAMPLE 10 - MULTIPLE GROUPS
Map<Integer, Double> peopleGroupedBySexAndAvgAge =
loadsOfPeople.stream()
.filter(x -> x.getIq() > 110)
.collect(
Collectors.groupingBy(Person::getAge,
Collectors.averagingInt(Person::getIq)));
We can group by multiple Collectors
Here we group by age and the average IQ of that group
{52=113.0, 60=117.5, 28=190.0}
44. STREAMS EXAMPLE 11 - FINDANY
loadsOfPeople.stream()
.filter(t -> t.getGender() == Person.Sex.FEMALE)
.findAny()
.ifPresent(System.out::println);
findAny either returns an element or nothing, hence we get
an Optional
ifPresent executes the Lambda if we get a result
45. STREAMS EXAMPLE 12 - PARALLEL
Lets iterate over 10,000,000 elements!
x -> Stream.iterate(1L, i -> i + 1)
.limit(x)
.reduce(Long::sum).get();
Executes in 80ms - we incur a penalty here because the long is
repeatedly boxed and unboxed
Executes in 211ms?! It turns out parallel isn't always a free win!
x -> Stream.iterate(1L, i -> i + 1)
.parallel().limit(x)
.reduce(Long::sum).get();
46.
47. STREAMS EXAMPLE 13 - PARALLEL WIN!
x -> LongStream.rangeClosed(1L, x)
.reduce(Long::sum).getAsLong();
Executes in 24ms - much better using an unboxed Stream
Executes in 7ms - now that the Stream isn't dynamic, parallel
works much better!
x -> LongStream.rangeClosed(1L, x)
.parallel()
.reduce(Long::sum).getAsLong();
48. OPTIONAL
Use Optional instead of passing null around, helps prevent
NullPointerExceptions
Optional is a container that’s either empty or present, in
which case it contains a value
So anytime that you’re thinking of return or accepting a null
value in a method, use Optional instead!
public class Computer {
private Optional<Mainboard> mainboard;
}
public class Mainboard {
private Optional<Cpu> cpu;
}
public class Cpu {
private String type;
}
49. USING OPTIONAL
Several ways to create an Optional:
Optional.of(cpu); //Throws an NPE if cpu is null
Optional.ofNullable(cpu); //empty if cpu is null
Getting the contents from the Optional:
cpu.get(); //get CPU or throw NoSuchElementException
cpu.orElse(new Cpu()); //safe get, provides default
And more...
cpu.isPresent(); //true if present, false if empty
cpu.ifPresent(x -> System.out.println(x.getType()));
Also supports map, atMap and lter!
50. OPTIONAL EXAMPLE 1 - BASICS
if (mainboard.isPresent() &&
mainboard.get().getCpu().isPresent()) {
mainboard.get().getCpu().get().getType();
}
Eww! Lets try something else!
***Fails to compile, calling getType on an Optional... if only
we could atten it???
Optional<String> cpuType =
mainboard.map(Mainboard::getCpu)
.map(Cpu::getType); //Optional<Optional<Cpu>>
51. OPTIONAL EXAMPLE 2 - FLATMAP
Optional<String> stringOptional = mainboard
.flatMap(Mainboard::getCpu)
.map(Cpu::getType);
Lets take this further and safely get cpu type of a Computer!
Computer computer = new Computer(mainboard);
String cpu = computer.getMainboard()
.flatMap(Mainboard::getCpu)
.map(Cpu::getType)
.filter(x -> "Intel".equals(x))
.orElse("Nothing we're interested in!");
52. IT'S BEEN EMOTIONAL...
Slides at
Source at
Follow me
markglh.github.io/Java8Madlab-Slides
github.com/markglh/Java8Madlab-Examples
@markglh
53. RECOMMENDED READING
Lambdas Part 1 (Ted Neward)
Lambdas Part 2 (Ted Neward)
Lambda Expressions vs Method References (Edwin Dalorzo)
Youtube: Java 8 - more readable and exible code (Raoul-
Gabriel Urma)
Youtube: Lambda Expressions & Streams (Adib Saikali)
Java 8 Streams Part 1 (Raoul-Gabriel Urma)
Java 8 Streams Part 2 (Raoul-Gabriel Urma)
Optional - No more NPE's (Raoul-Gabriel Urma)