Download - Java 8: more readable and flexible code
Wri$ng more concise and flexible code with Java 8
Raoul-‐Gabriel Urma @raoulUK
1
uname -‐a • PhD at University of Cambridge (2011 -‐ current) – Source code analysis, automated refactoring, type systems, programming languages
• MEng Imperial College London (2007 -‐ 2011) • Google (Python team), Oracle (Java team), Goldman Sachs,
eBay • Regular author for Oracle Java Magazine • Conference speaker: Fosdem, Devoxx…
2
What I’m going to cover
• Why Java 8? (5min) • Behaviour parameterisa$on (10min) • Lambda expressions (20min) • Streams (15min) • Op$onal (5min) • Default methods (10min)
3
Java 8 in Ac$on: Lambdas, Streams and func$onal-‐style programming
4
• Co-‐authored with Mario Fusco & Alan Mycro` • Most complete book on Java 8
h"p://manning.com/urma
Why Java 8
Before: Collec$ons.sort(inventory, new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
A5er: inventory.sort(comparing(Apple::getWeight));
5
Why Java 8?
6
List<Dish> lowCaloricDishes = new ArrayList<>(); for(Dish d: dishes){ if(d.getCalories() < 400){ lowCaloricDishes.add(d); } } List<String> lowCaloricDishesName = new ArrayList<>(); Collec$ons.sort(lowCaloricDishes , new Comparator<Dish>() { public int compare(Dish d1, Dish d2){ return Integer.compare(d1.getCalories(), d2.getCalories()); } }); for(Dish d: lowCaloricDishes){ lowCaloricDishesName.add(d.getName()); } 6
sor$ng by calories
filtering low calories
Extract names
Why Java 8?
7 7
List<String> lowCaloricDishesName = dishes.stream() .filter(d -‐> d.getCalories() < 400) .sorted(comparing(Dish::getCalories)) .map(Dish::getName) .collect(toList());
Why Java 8? ( as seen by language designers)
• Commodity CPUs are mul$core (e.g. 4 cores) • Analogy: you have 4 assistants, in theory you could get the work done 4 $mes faster!
• In prac$ce: – it’s hard because you now have to figure out how to distribute a piece of work amongst 4 people.
– It’s easier to just pass the whole piece of work to one person.
8
Why Java 8? ( as seen by language designers)
• Vast majority of Java programs use only one of these cores and leave the others idle
• Why? wri$ng efficient parallel code is hard – summing an array with a for loop is easy – how to sum an array on 4 cores?
9
Why Java 8? ( as seen by language designers)
• Java 8 introduces features that make parallel data processing easier
• Driven from three concepts: – stream processing – behaviour parameterisa$on ("passing code") – no shared mutable data
10
Stream processing
cat file1 file2 | tr "[A-‐Z]" "[a-‐z]" | sort | tail -‐3
11
!
Stream processing List<String> lowCaloricDishesName = dishes.parallelStream() .filter(d -‐> d.getCalories() < 400) .sorted(comparing(Dish::getCalories)) .map(Dish::getName) .collect(toList());
12
stream processing
Behaviour parameterisa$on
13
Behaviour parameterisa$on
List<String> lowCaloricDishesName = dishes.parallel() .filter(d -‐> d.getCalories() < 400) .sorted(comparing(Dish::getCalories)) .map(Dish::getName) .collect(toList());
14
lambda expression
method reference
No shared mutable data List<String> lowCaloricDishesName = dishes.parallelStream() .filter(d -‐> d.getCalories() < 400) .sorted(comparing(Dish::getCalories)) .map(Dish::getName) .collect(toList());
15
Can be duplicated and ran on disjoint part of input
Java 8
• Introduces a concise way to pass behaviour – lambda expressions, method references
• Introduces an API to process data in parallel – Streams API – several opera$ons such as filter, map, reduce can be parameterised with lambdas
• Also: default methods (more later) – more flexible inheritance
16
Behaviour parameterisa$on
• Goal: abstract over behaviour – Do <something> for every element in a list – Do <something> else when the list is finished – Do <yet something else> if an error occurs
• Why should you care? – Adapt to changing requirements – Java 8 Streams API heavily relies on it
17
1st awempt: fitering green apples public sta$c List<Apple> filterGreenApples(List<Apple> inventory) { List<Apple> result = new ArrayList<>(); for(Apple apple: inventory){ if("green".equals(apple.getColor() ) { result.add(apple); } } return result; }
18
2nd awempt: abstrac$ng color public sta$c List<Apple> filterApplesByColour(List<Apple> inventory, String color) { List<Apple> result = new ArrayList<>(); for (Apple apple: inventory){ if (apple.getColor().equals(color))) { result.add(apple); } } return result; }
19
List<Apple> greenApples = filterApplesByColor(inventory, "green"); List<Apple> redApples = filterApplesByColor(inventory, "red");
2nd awempt: abstrac$ng weight public sta$c List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) { List<Apple> result = new ArrayList<>(); for (Apple apple: inventory){ if (apple.getWeight() > weight) { result.add(apple); } } return result; }
20
List<Apple> heavyApples = filterApplesByWeight(inventory, 150); List<Apple> megaHeavyApples = filterApplesByWeight(inventory, 250);
3rd awempt: filtering with everything
public sta$c List<Apple> filter (List<Apple> inventory, String color, int weight, boolean flag) { List<Apple> result = new ArrayList<>(); for (Apple apple: inventory){ if ( (flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight) ){ result.add(apple); } } return result; }
21
List<Apple> greenApples = filter(inventory, "green", 0, true); List<Apple> heavyApples = filter(inventory, "", 150, false);
4th (a) awempt: modeling selec$on criteria
public interface ApplePredicate{ public boolean test (Apple apple); } public class AppleWeightPredicate implements ApplePredicate{ public boolean test(Apple apple){ return apple.getWeight() > 150; } } public class AppleColorPredicate implements ApplePredicate{ public boolean test(Apple apple){ return "green".equals(apple.getColor()); } }
22
4th (a) awempt: modeling selec$on criteria
23
4th (b) awempt: filtering by an abstract criteria
public sta$c List<Apple> filter(List<Apple> inventory, ApplePredicate p){ List<Apple> result = new ArrayList<>(); for(Apple apple: inventory){ if(p.test(apple)){ result.add(apple); � } } return result; }
24
Let’s pause for a liwle bit
• Our code is much more flexible! – can create any kinds of selec$on criteria on an Apple – re-‐use of code for filter
• However, declaring many selec$on criterias using classes is verbose… L – we have the right abstrac$on but not good concision – we need a bewer way to create and pass behaviours
25
5th awempt: anonymous classes List<Apple> result = filter(inventory, new ApplePredicate() { public boolean test(Apple apple){ return "red".equals.(apple.getColor()); } }); List<Apple> result = filter(inventory, new ApplePredicate() { public boolean test(Apple apple){ return apple.getWeight() > 150; } }); 26
6th awempt: Java 8 lambdas List<Apple> result = filter(inventory, (Apple apple) -‐> "red".equals.(apple.getColor())); List<Apple> result = filter(inventory, (Apple apple) -‐> apple.getWeight() > 150);
27
Great because closer to problem statement!
7th awempt: abstrac$ng over the list type
public sta$c <T> List<T> filter (List<T> list, Predicate<T> p) { List<T> result = new ArrayList<>(); for(T e: list){ if(p.test(e)) { result.add(e); } } return result; }
28
8th awempt: Java 8 lambdas again List<String> result = filter(strings, (String s) -‐> s.endsWith("JDK")); List<Integer> result = filter(numbers, (Integer i) -‐> i % 2 == 0); List<Apple> result = filter(inventory, (Apple a) -‐> apple.getWeight() > 150);
29
Moral of the story
• Behaviour parameterisa$on lets you write more flexible code – Adapt for changes – Avoids code duplica$on
• To encourage this style of programming we need a concise way to create and pass behaviours – Java 8 brings lambda expressions to help!
30
Flexible
Concise
Anonymous classes
Verbose
Rigid
Lambdas Class + Instance
Value parameterisa$on
Behaviour parameterisa$on
Value vs Behaviour parameterisa$on
Lambda expressions: what is it?
• A lambda expression is: – a kind of anonymous func$on – that can be passed around: – It doesn’t have a name, but it has a list of parameters, a body, a return type, and also possibly a list of excep$ons that can be thrown.
32
In a nutshell
33
Lambda expressions: what is it?
• A lambda expression is: – a kind of anonymous func$on
• Anonymous: doesn’t have an explicit name like a method: less to write and think about!
• Func$on: not associated to a class like a method is
34
Lambda expressions: what is it?
• A lambda expression is: – a kind of anonymous func$on – that can be passed around:
• Passed around: A lambda expression can be passed as argument to a method or stored in a variable
35
Lambda expressions: what is it?
• A lambda expression is: – a kind of anonymous func$on – that can be passed around: – it doesn’t have a name, but it has a list of parameters, a body, a return type, and also possibly a list of excep$ons that can be thrown.
• Concise: you don’t need to write a lot of boilerplate like you do for anonymous classes.
36
Goal
• Goal: let you pass a piece of behaviour/code in a concise way
• You can cope with changing requirements by using a behaviour, represented by a lambda, as a parameter to a method
• You no longer need to choose between abstracaon and concision!
37
Before/A`er
Before: inventory.sort(new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
A5er: inventory.sort((Apple a1, Apple a2) -‐> a1.getWeight().compareTo(a2.getWeight()));
38
Examples
39
Use case Example of lambda A boolean expression (List<String> list) -‐> list.isEmpty() Crea$ng objects () -‐> new Apple(10) Consuming from an object (Apple a) -‐> System.out.println(a.getWeight()) Select/extract from an object (String s) -‐> s.length() Combine two values (int a, int b) -‐> a * b Compare two objects (Apple a1, Apple a2) -‐>
a1.getWeight().compareTo(a2.getWeight())
Where and how to use lambdas?
40
• Java lets you pass expressions only where a type is expected
• So what’s the type of a lambda expression?
Func$onal interface (1)
41
• The type of a lambda expression is essen$ally a funcaonal interface
• A func$onal interface is an interface that declares exactly one (abstract) method.
Func$onal interface (2)
42
// java.u$l.Comparator public interface Comparator<T> { public int compare(T o1, T o2); } public interface Runnable { public void run(); } public interface ApplePredicate { public boolean test (Apple a); }
Func$onal interface & Lambdas
43
• The signature of the abstract method of the func$onal interface essen$ally describes the signature of the lambda expression.
• Lambda expressions let you provide the implementa$on of the abstract method of a func$onal interface directly inline and treat the whole expression as an instance of a func$onal interface.
Func$onal interface & Lambdas
44
Runnable r1 = () -‐> System.out.println("Hello World 1"); Runnable r2 = new Runnable(){ public void run(){ System.out.println("Hello World 2"); } } public void process(Runnable r){ r.run(); } process(r1); process(r2); process(() -‐> System.out.println("Hello World 3"));
Execute Around Pawern
45
Open ressource
Do some processing
Close ressource
Execute Around Pawern
46
public sta$c String processFile() throws IOExcep$on { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return br.readLine(); } }
• This current code is limited. You can read only the first line of the file.
What we want
47
String result = processFile((BufferedReader br) -‐> br.readLine()); String result = processFile((BufferedReader br) -‐> br.readLine() + br.readLine());
Step 1: Introducing func$onal interface
48
public interface BufferedReaderProcessor { public String process(BufferedReader b) throws IOExcep$on; } public sta$c String processFile(BufferedReaderProcessor p) throws IOExcep$on { ... }
Step 2: Execu$ng a behaviour
49
public sta$c String processFile(BufferedReaderProcessor p) throws IOExcep$on { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return p.process(br); } } String result = processFile((BufferedReader br) -‐> br.readLine()); String result = processFile((BufferedReader br) -‐> br.readLine() + br.readLine());
Lots of func$onal interfaces
50
Funcaonal interface Lambda signature Predicate<T> T -‐> boolean Consumer<T> T -‐> void Func$on<T, R> T -‐> R Supplier<T> () -‐> T UnaryOperator<T> T -‐> T Binaryoperator<T> (T, T) -‐> T BiFunc$on<T, U, R> (T, U) -‐> R
• Have a look in java.u$l.func$on.*
Method references
51
• Method references let you reuse exis$ng method defini$ons and pass them just like lambdas. « First-‐class » func$ons
• Just a syntac'c suggar to lambdas
Before: (Apple a) -‐> a.getWeight() A5er: Apple::getWeight Before: (str, i) -‐> str.substring(i) A5er: String::substring
Recipes
52
Bewer code with Java 8: a prac$cal example
53
public class AppleComparator implements Comparator<Apple> { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } } inventory.sort(new AppleComparator());
Anonymous class
54
inventory.sort(new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
Lambdas
55
inventory.sort( (Apple a1, Apple a2) -‐> a1.getWeight().compareTo(a2.getWeight()) );
Lambdas
56
inventory.sort( (Apple a1, Apple a2) -‐> a1.getWeight().compareTo(a2.getWeight()) ); With type inference: inventory.sort( (a1, a2) -‐> a1.getWeight().compareTo(a2.getWeight()) );
A bit of help from library
57
Comparator<Apple> byWeight = Comparator.comparing((Apple a) -‐> a.getWeight()); inventory.sort(byWeight);
Method references
58
Comparator<Apple> byWeight = Comparator.comparing(Apple::getWeight); inventory.sort(byWeight);
Tidy up
59
import sta$c java.u$l.Comparator.comparing; inventory.sort(comparing(Apple::getWeight)); Reads like probable statement!
Streams
60
• Nearly every Java applica$ons makes and processes collec$ons
• However processing collec$ons is far from perfect: – SQL like opera$ons? – How to efficiently process large collec$ons?
SQL-‐like opera$ons
61
• Many processing pawerns are SQL-‐like – finding a transac$on with highest value – grouping transac$ons related to grocery shopping
• Re-‐implemented every $me • SQL is declaraave – express what you expect not how to implement a query
– SELECT id, MAX(value) from transac$ons
Example: Java 7
62
List<Transac$on> groceryTransac$ons = new Arraylist<>(); for(Transac$on t: transac$ons){ if(t.getType() == Transac$on.GROCERY){ groceryTransac$ons.add(t); } } Collec$ons.sort(groceryTransac$ons, new Comparator(){ public int compare(Transac$on t1, Transac$on t2){ return t2.getValue().compareTo(t1.getValue()); } }); List<Integer> transac$onIds = new ArrayList<>(); for(Transac$on t: groceryTransac$ons){ transac$onsIds.add(t.getId()); }
sor$ng by decreasing value
filtering grocery transac$ons
extract Ids
Example: Java 8
63
List<Integer> transac$onsIds = transacaons.stream() .filter(t -‐> t.getType() == Transac$on.GROCERY) .sorted(comparing(Transac$on::getValue).reversed()) .map(Transac$on::getId) .collect(toList());
Example: Java 8 -‐ parallel
64
List<Integer> transac$onsIds = transacaons.parallelStream() .filter(t -‐> t.getType() == Transac$on.GROCERY) .sorted(comparing(Transac$on::getValue).reversed()) .map(Transac$on::getId) .collect(toList());
Ok cool – so what’s a Stream?
65
• Informal: a fancy iterator with database-‐like opera$ons
• Formal: A sequence of elements from a source that supports aggregate opera$ons.
Ok cool – so what’s a Stream?
66
• Sequence of elements: a stream provides an interface to a sequenced set of values of a specific element type. However, streams don’t actually store elements, they are computed on demand.
Ok cool – so what’s a Stream?
67
• Source: Streams consume from a data-‐providing source such as Collec$ons, Arrays, or IO resources.
Ok cool – so what’s a Stream?
68
• Aggregate operaaons: Streams support SQL-‐like opera$ons and common opera$ons from func$onal programing languages such as filter, map, reduce, find, match, sorted etc.
Two addi$onal proper$es
69
• Pipelining: Many stream opera$ons return a stream themselves. This allows opera$ons to be chained and form a larger pipeline as well as certain op$misa$ons (more later).
• Internal iteraaon: In contrast to collec$ons, that are iterated explicitly (“external itera$on”), stream opera$ons do the itera$on behind the scene for you.
70
Pipelining
• Intermediate operaaons: return a Stream and can be “connected”
• Terminal operaaons: computes the result of the pipeline
Intermediate opera$ons Terminal opera$on
71
Internal Itera$on
72
Laziness & short-‐circui$ng
73
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); List<Integer> twoEvenSquares = numbers.stream() .filter(n -‐> n % 2 == 0) .map(n -‐> n * n) .limit(2) .collect(toList());
Laziness & loop fusion & short-‐circui$ng
74
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); List<Integer> twoEvenSquares = numbers.stream() .filter(n -‐> n % 2 == 0) .map(n -‐> n * n) .limit(2) .collect(toList());
• filtering 1 • filtering 2 • mapping 2 • filtering 3 • filtering 4 • mapping 4
Many opera$ons
75
• Filtering: filter, dis$nct, limit, skip • Finding/Matching: anyMatch, allMatch, noneMatch, findAny, findFirst
• Mapping: map, flatMap • Reducing: reduce
Streams: much more (1) Map<Dish.Type, List<Dish>> dishesbyType = new HashMap<>(); for (Dish dish: menu) {
Dish.Type type= dish.getType(); List<Dish> dishesForType = dishesbyType .get(type); if (dishesForType == null) { dishesForType = new ArrayList<>(); dishesbyType .put(type, dishesForType); } dishesForType .add(dish);
}
76
Streams: much more(2)
77
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType)); {FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}
Streams: much more(3) Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream().collect(
paraaoningBy(Dish::isVegetarian, groupingBy(Dish::getType))); • Produces a two-‐level Map: {false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]}, true={OTHER=[french fries, rice, season fruit, pizza]}}
78
Step 1: sequen$al for loop
public sta$c long itera$veSum(long n) { long result = 0; for (long i = 0; i < n; i++) { result += i; } return result; }
79
3ms
Step 2: sequen$al stream public sta$c long sequen$alSum(long n) { return Stream.iterate(1L, i -‐> i + 1) .limit(n) .reduce(Long::sum).get(); }
80
98ms
Step 3: parallel stream public sta$c long sequen$alSum(long n) { return Stream.iterate(1L, i -‐> i + 1).parallel() .limit(n) .reduce(Long::sum).get(); }
81
193ms!!
Step 4: improved sequen$al stream
public sta$c long rangedSum(long n) { return LongStream.rangeClosed(1, n) .reduce(Long::sum).getAsLong(); }
82
17ms
Step 5: improved parallel stream public sta$c long rangedSum(long n) { return LongStream.rangeClosed(1, n).parallel() .reduce(Long::sum).getAsLong(); }
83
1ms
Streams: much more (1)
84
• Infinite streams • Collectors: – collect – groupingBy – par$$onBy
Op$onal<T>
85
• A new class added in Java8: java.u$l.Op$onal • Indicates the presence or absence of a value • has a bunch of method to simplify and force “null” checking
Op$onal<Transac$on> optTransac$on = numbers.stream().findAny(t -‐> t.getValue() < 200);
Create Op$onal
86
opt = Op$onal.of(notNull); opt = Op$onal.ofNullable(mayBeNull); opt = Op$onal.empty();
Op$onal: do something if there’s a value
87
Instead of: if(transac$on != null){ System.out.println(t); } Write: optTransac$on.ifPresent(System.out::println);
Op$onal: reject certain values
88
Instead of: if(transac$on != null && transac$on.getId() > 10){ System.out.println(t); } Write: optTransac$on.filter(t -‐> t.getId() > 10) .ifPresent(System.out::println);
Op$onal: transform value if present
89
Instead of: if(transac$on != null){ String group = transac$on.getGroup(); if(“shopping”.equals(group)){ System.out.println(t); } } Write: optTransac$on.map(Transac$on::getGroup) .filter(g -‐> “shopping”.equals(g)) .ifPresent(System.out::println);
Op$onal: default value
90
Instead of: int len = (list != null)? list.size() : -‐1; Write: int len = list.map(List::size).orElse(-‐1);
Op$onal: nested checking
91
Instead of: if(transac$on == null){ return “Unknown”; } Des$nator des$nator = transac$on.getDes$nator(); if(des$nator == null){ return “Unknown”; } country = des$nator.getCountry(); if(country == null){ return “Unknown”; } return country.getName() != null ? country.getName() : “Unknown”;
Op$onal: nested checking
92
transac$on.flatMap(Transac$on::getDes$nator) .flatMap(Transac$on::getCountry) .map(Country::getName) .orElse(“Unknown”);
Default methods
93
• Interfaces in Java 8 can contain implementa$on code
From List.java: default void sort(Comparator<? super E> c){ Collec$ons.sort(this, c); }
Goal
94
• Evolve libraries • Adding a method to an interface is source incompa$ble – all implementers need to support the method – recompiling a class implemen$ng the interface will fail
• Providing a “default” implementa$on solves the problem
95
Applica$ons: Op$onal methods
96
interface Iterator<T> { boolean hasNext(); T next(); default void remove() { throw new UnsupportedOpera$onExcep$on(); } }
Applica$ons: mul$ple inheritance
97
• A class has been allowed to implement mul$ple interfaces since day 1 of Java!
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable, Iterable<E>, Serializable { … }
Applica$ons: mul$ple inheritance
98
Applica$ons: mul$ple inheritance
99
public interface Sized { public int size(); public default boolean isEmpty(){ return size() == 0; } }
Applica$ons: mul$ple inheritance
100
public interface Foreachable<T> extends Iterable<T> { public default void forEach(Consumer<? super T> block){ for(T e: this){ block.accept(e); } } }
Applica$ons: mul$ple inheritance
101
public interface Removeable<T> extends Iterable<T> { public default boolean removeIf(Predicate<? super T> filter){ boolean removed = false; Iterator<T> each = iterator(); while(each.hasNext()) { if(filter.test(each.next())) { each.remove(); removed = true; } } return removed; } }
Applica$ons: mul$ple inheritance
102
Resolu$on rules
103
1. Classes always win. A method declara$on in the class or a superclass takes priority over any default method declara$on. 2. Otherwise, the method with the same signature in the most specific default-‐providing interface is selected. (If B extends A, B is more specific than A).
Use case 1
104
Use case 1
105
public interface A{ public default void hello() { System.out.println("Hello from A"); } } public interface B extends A{ public default void hello() { System.out.println("Hello from B"); } } public class C implements B, A { public sta$c void main(String... args) { new C().hello(); // ?? } }
Use case 2 (a)
106
public class D implements A{ } public class C extends D implements B, A { public sta$c void main(String... args) { new C().hello(); // ?? } }
Use case 2 (b)
107
public class D implements A{ public void hello(){ System.out.println(“Hello from D”); } } public class C extends D implements B, A { public sta$c void main(String... args) { new C().hello(); // ?? } }
Use case 3
108
Explicit disambigua$on
109
public class C implements B, A { public void hello(){ B.super.hello(); } }
Use case 4
110
More…
111
• Func$onal asynchronous programming with CompleatableFuture
• Performance, Spliterators • Recipes using Op$onal • Tools, tes$ng & debugging • Func$onal programming in Java • Good ideas from Scala • Date/Time API, Java bytecode & lambdas
• Get the book Java 8 in Acaon to find out more!!! • h"p://manning.com/urma/ 38% discount code: vturma01