Java 8 Workshop
-
Upload
mario-fusco -
Category
Software
-
view
2.267 -
download
5
Transcript of Java 8 Workshop
by Mario [email protected]: @mariofusco
8 in ActionLambdas, Streams, and functional-style programming
Project Lambda – A Minimal History➢ 2006 – Gosling: "We will never have lambdas in Java"➢ 2007 – 3 different proposals for lambdas in Java➢ 2008 – Reinhold: "We will never have lambdas in Java"➢ 2009 – Start of project Lambda (JSR 335)
public boolean willJavaHaveLambdas() { return currentYear % 2 == 1;}
From Single Method Interfaces …public interface Comparator<T> { int compare(T o1, T o2); }
Collections.sort(strings, new Comparator<String>() { public int compare(String s1, String s2) { return s1.compareToIgnoreCase(s2); } });
➢ Bulky syntax➢ Confusion surrounding the meaning of names and this➢ Inability to capture non-final local variables➢ Inability to abstract over control flow
Functional Interface
… To Lambda ExpressionsCollections.sort(strings,(s1, s2) -> s1.compareToIgnoreCase(s2));
Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);
Lambda expression are always converted to instance of a functional interface
Compiler figures out the types
No need of changing the JVM to create a new type for lambda expressions
Common JDK8 @FunctionalInterfaces
Give a look at java.util.function.*
➢ Predicate<T> → test a property of the object passed as argument
➢ Consumer<T> → execute an action on the object passed as argument
➢ Function<T, U> → transform a T to a U
➢ BiFunction<T, U, V> → transform a (T, U) to a V
➢ Supplier<T> → provide an instance of a T (such as a factory)
➢ UnaryOperator<T> → a unary operator from T -> T
➢ BinaryOperator<T> → a binary operator from (T, T) -> T
Anatomy of a lambda expression
s -> s.length()
(int x, int y) -> x + y
() -> 42
(x, y, z) -> { if (x) { return y; } else { return z; } }
The formal parameters of a lambda expression may have either inferred or declared types
A lambda expression is like an (anonymous) method: it provides a list of formal parameters and a body
A lambda body is either a single expression or a block
Return is implicit and can be omitted
However …
… syntax is probably the less important thing about lambda expression …
… the really fundamental thing about lambda expression is …
… the huge paradigm shift they imply
Why Lambdas?➢ Behaviors can be passed to a method together with data➢ API designers can build more powerful, expressive APIs➢ More room for generalization
➢ Pass behaviors to a method together with normal data➢ Libraries remain in control of computation
➢ e.g. internal vs. external iteration➢ More opportunities for optimization
➢ Laziness➢ Parallelism➢ Out-of-order execution
➢ More regular and then more readable code➢ e.g. nested loops vs. pipelined (fluent) operations
➢ Better composability and reusability
Example 1Your first Lambda
Internal vs External Iteration
for (Employee e : employees) { e.setSalary(e.getSalary() * 1.03); }
employees.forEach(e -> e.setSalary(e.getSalary() * 1.03));
Inherently serial Client has to manage iteration Nested loops are poorly readable
+ Library is in control → opportunity for internal optimizations as parallelization, lazy evaluation, out-of-order execution+ More what, less how → better readability+ Fluent (pipelined) operations → better readability+ Client can pass behaviors into the API as data → possibility to abstract and generalize over behavior → more powerful, expressive APIs
Not only a syntactic change!
Internal vs External Iteration
Sorting with LambdasComparator<Person> byAge = new Comparator<Person>() { public int compare(Person p1, Person p2) { return p1.getAge() – p2.getAge(); } };
Collections.sort(people, byAge);
Comparator<Person> byAge = (p1, p2) -> p1.getAge() – p2.getAge();
Functional interface
Lambda expression
Collections.sort(people, (p1, p2) -> p1.getAge() – p2.getAge());
Can We Do Better?
Collections.sort(people, comparing(Person::getAge));
Method reference
Comparator<Person> byAge = Comparators.comparing(p -> p.getAge());
Comparator<Person> byAge = Comparators.comparing(Person::getAge);
Collections.sort(people, comparing(Person::getAge) .compose(comparing(Person::getName)));
Collections.sort(people, comparing(Person::getAge).reverse());
Reusability
Readability
Composability
Example 2Passing behavior
with Lambda
OOP makes code understandable by encapsulating moving parts
FP makes code understandable by minimizing moving parts
- Michael Feathers
OOP vs FP
The OOP/FP dualism - OOPpublic class Bird { }
public class Cat { private Bird catch; private boolean full; public void capture(Bird bird) { catch = bird; }
public void eat() { full = true; catch = null; }}
Cat cat = new Cat();Bird bird = new Bird();
cat.capture(bird);cat.eat();
The story
The OOP/FP dualism - FPpublic class Bird { }
public class Cat { public CatWithCatch capture(Bird bird) { return new CatWithCatch(bird); }}
public class CatWithCatch { private final Bird catch; public CatWithCatch(Bird bird) { catch = bird; } public FullCat eat() { return new FullCat(); }}
public class FullCat { }
BiFunction<Cat, Bird, FullCat> story = ((BiFunction<Cat, Bird, CatWithCatch>)Cat::capture) .compose(CatWithCatch::eat);
FullCat fullCat = story.apply( new Cat(), new Bird() );
Immutability
Emphasis on verbs instead of names
No need to test internal state: correctness enforced by the compiler
Streams - Efficiency with laziness
Represents a sequence of element from a sourceNot a data structure: doesn't store elements but compute them on demandSources can be Collection, array, generating function, I/O ....Encourages a pipelined ( "fluent" ) usage styleOperations are divided between intermediate and terminalLazy in nature: only terminal operations actually trigger a computation
List<Employee> employess = ...employees.stream() .filter(e -> e.getIncome() > 50000) .map(e -> e.getName()) .forEach(System.out::println);
Evolving APIs with default methodsWhere does that stream() method come from?
public interface Collection<E> extends Iterable<E> { ... default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }}
✔ Multiple inheritance of type since Java 1.0✔ Java 8 introduces multiple inheritance of behavior✔ No multiple inheritance of state (like in Scala's traits)
Default methods resolution rules1.Classes always win: a method declaration in the class or a
superclass takes priority over any default method declaration.2.Then sub-interfaces win: the method with the same signature in
the most specific default-providing interface is selected.3.If the choice is still ambiguous, the class inheriting from multiple
interfaces has to explicitly select which default method implementation to use by overriding it and calling the desired method explicitly.
public interface A { default void hello() { System.out.println("Hello from A"); }}public interface B { default void hello() { System.out.println("Hello from B"); }}public class C implements B, A { void hello() { B.super.hello(); }}
Example 3Stream laziness
Intermediate & Terminal Operations
Intermediatedistinctmap, flatMaplimit, skippeeksorted
TerminalcollectcountforEachmin, maxreducetoArrayfindAny, findFirstallMatch, anyMatch, noneMatch
Return another Stream and then can be chained to form a pipeline of operations
Return the final result of the operation pipeline
map : an intermediate operation
map( -> )
Stream
reduce : a terminal operation
Stream
Integerreduce(0, (a, b) -> a + b)
Putting them all together
List<String> strings = asList("Lambda", "expressions", "are", "easy", "and", "useful");
int totalLength = strings.stream() .map(String::length) .reduce(0, (a, b) -> a + b);
Stream Operations PipeliningIntermediate Operations are lazy and return another
Stream allowing to fluently pipeline other operations
Terminal Operations are eager and computes the result of the whole pipeline
Example 4Prime numbers
List<String> errors = Files.lines(Paths.get(fileName)) .filter(l -> l.startsWith("ERROR") .limit(40) .collect(toList());
Separation of ConcernsList<String> errors = new ArrayList<>();int errorCount = 0;File file = new File(fileName);String line = file.readLine();while (errorCount < 40 && line != null) { if (line.startsWith("ERROR")) { errors.add(line);
errorCount++; } line = file.readLine();}
Example 5Stream from a File
GroupingMap<Dish.Type, List<Dish>> dishesByType = new HashMap<>();for (Dish dish : menu) { Dish.Type type = dish.getType(); List<Dish> dishes = dishesByType.get(type); if (dishes == null) { dishes = new ArrayList<>(); dishesByType.put(type, dishes); } dishes.add(dish); }
Grouping with Collectors
classify item into list
keyapply
next item
grouping Map
fish meat other
salmon pizza rice
french fries
pork beef
chicken
prawnsClassification
Function fish
Stream
Map<Dish.Type, List<Dish>> dishesByType = menu.stream() .collect(groupingBy(Dish::getType));
Example 6Grouping
groupingBy
Dish::getType
collectingAndThen
reducing
Optional::get
Optional[pork]
grouping Map
fish meat other
salmon
pizzapork
result
result
Stream
subStreamsubStreamsubStream
collectingAndThen
reducing
collectingAndThen
reducing
resultresult
classification function
The original Stream is divided in subStreams according to the classification function
Each subStreams is independently processed by the second Collector
transformation function
The reducing Collector returns the most caloric Dish wrapped in an Optional
The collectingAndThen Collector returns the value extracted from the former Optional
The results of the 2nd level Collectors become the values of the grouping map
Complex Grouping
employees.stream() .filter(e -> e.getRole() == Role.MANAGER) .map(Employee::getIncome) .reduce(0, (a, b) -> a + b);
parallelStream()
Streams – Parallelism for free
A parallel reduction
Example 7Parallel Streams
Is there such thing as a free lunch?
Probably yes …
… but we need functional forks and knives to eat it
Concurrency & Parallelism
Parallel programmingRunning multiple tasks at
the same time
Concurrent programmingManaging concurrent requests
Both are hard!
The cause of the problem …
Mutable state +Parallel processing =Non-determinism
FunctionalProgramming
Too hard to think about them!
Race conditions
Deadlocks
Starvation
Livelocks
… and its effects
The native Java concurrency model
Based on:
They are sometimes plain evil …
… and sometimes a necessary pain …
… but always the wrong default
Threads
Semaphores
SynchronizationLocks
Different concurrency models
Isolated mutable state (actors)
Purely immutable (pure functions)
Shared mutable state (threads + locks)
Summing attendants ages (Threads)
class Blackboard { int sum = 0; int read() { return sum; } void write(int value) { sum = value; }}
class Attendant implements Runnable { int age; Blackboard blackboard;
public void run() { synchronized(blackboard) { int oldSum = blackboard.read(); int newSum = oldSum + age; blackboard.write(newSum); } }}
Summing attendants ages (Actors)
class Blackboard extends UntypedActors { int sum = 0; public void onReceive(Object message) { if (message instanceof Integer) { sum += (Integer)message; } }}
class Attendant { int age; Blackboard blackboard;
public void sendAge() { blackboard.sendOneWay(age); }}
Summing attendants ages (Functional)
class Blackboard { final int sum; Blackboard(int sum) { this.sum = sum; }}
class Attendant { int age; Attendant next;
public Blackboard addMyAge(Blackboard blackboard) { final Blackboard b = new Blackboard(blackboard.sum + age); return next == null ? b : next.myAge(b); }}
The state quadrantsMutable
Immutable
Shared
Unshared
Actors
FunctionalProgramming
Threads
Determinism
Non-determinism
Reassigning a variableModifying a data structure in placeSetting a field on an objectThrowing an exception or halting with an errorPrinting to the console Reading user inputReading from or writing to a fileDrawing on the screen
A program created using only pure functions
What is a functional program?
No side effects allowed like:
Functional programming is a restriction on how we write programs, but not on what they can do
}}
avoidable
deferrable
Example 8Modularity to
confine side-effects
A pure functional core
functionalcore
a thin external layer to handle side-effects
Any function with side-effects can be split into a pure function at the core and a pair of functions with side-effect. This transformation can be repeated to push side-effects to the outer layers of the program.
What is a (pure) function?A function with input type A and output type B is a computation which relates every value a of
type A to exactly one value b of type B such that b is determined solely by the value of a
But, if it really is a function, it will do
nothing else
Referential transparencyAn expression e is referentially transparent if for all programs p,
all occurrences of e in p can be replaced by the result of evaluating e, without affecting the observable behavior of p
A function f is pure if the expression f(x) is referentially transparent for all referentially transparent x
RTString x = "purple";String r1 = x.replace('p', 't');String r2 = x.replace('p', 't');
String r1 = "purple".replace('p', 't'); r1: "turtle"String r2 = "purple".replace('p', 't'); r2: "turtle"
StringBuilder x = new StringBuilder("Hi");StringBuilder y = x.append(", mom");String r1 = y.toString();String r2 = y.toString();
String r1 = x.append(", mom").toString(); r1: "Hi, mom"String r2 = x.append(", mom").toString(); r1: "Hi, mom, mom"
Non-RT
vs.
RT winsUnder a developer point of view: Easier to reason about since effects of evaluation are purely
localUse of the substitution model: it's possible to replace a term
with an equivalent one
Under a performance point of view:The JVM is free to optimize the code by safely reordering the
instructionsNo need to synchronize access to shared dataPossible to cache the result of time consuming functions
(memoization), e.g. with
Map.computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
Mutability
Parameter binding is about assigning names to things
Mutating variables is about assigning things to names
Does that second one sound weird?
… well it's because
it IS weird
Immutability
Immutable objects can be shared among many threads exactly because none of them can modify it
In the same way immutable (persistent) data structures can be shared without any need to synchronize the different threads accessing them
5
8
7 9
3
4
E E E E E E
E
5
3
2
E E
Persistent Collections
Shared data
Old Root
New Root
Example 9Avoiding mutability
NullPointerException
Raise your hand if you've ever seen this
Null references? No, Thanks✗ Errors source → NPE is by far the most common exception in Java✗ Bloatware source → Worsen readability by making necessary to fill our code
with null checks✗ Meaningless → Don't have any semantic meaning and in particular are the
wrong way to model the absence of a value in a statically typed language✗ Breaks Java philosophy → Java always hides pointers to developers, except
in one case: the null pointer✗ A hole in the type system → Null has the bottom type, meaning that it can
be assigned to any reference type: this is a problem because, when propagated to another part of the system, you have no idea what that null was initially supposed to be
Tony Hoare, who invented the null reference in 1965 while working on an object oriented language called ALGOL W, called its invention his
“billion dollar mistake”
Replacing nulls with Optionals
If nulls are so problematic why don't we just avoid them?
value
Optional
value
EMPTY
Optional
null
Optional is a type that models a possibly missing value
public class Person { private Car car; public Car getCar() { return car; }}
public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; }}
public class Insurance { private String name; public String getName() { return name; }}
Finding Car's Insurance Name
String getCarInsuranceName(Person person) { if (person != null) { Car car = person.getCar(); if (car != null) { Insurance insurance = car.getInsurance(); if (insurance != null) { return insurance.getName() } } } return "Unknown";}
Attempt 1: deep doubts
Attempt 2: too many choicesString getCarInsuranceName(Person person) { if (person == null) { return "Unknown"; } Car car = person.getCar(); if (car == null) { return "Unknown"; } Insurance insurance = car.getInsurance(); if (insurance == null) { return "Unknown"; } return insurance.getName()}
Optional to the rescuepublic class Optional<T> { private static final Optional<?> EMPTY = new Optional<>(null); private final T value; private Optional(T value) { this.value = value; }
public<U> Optional<U> map(Function<? super T, ? extends U> f) { return value == null ? EMPTY : new Optional(f.apply(value)); }
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> f) { return value == null ? EMPTY : f.apply(value); }}
public class Person { private Optional<Car> car; public Optional<Car> getCar() { return car; }}
public class Car { private Optional<Insurance> insurance; public Optional<Insurance> getInsurance() { return insurance; }}
public class Insurance { private String name; public String getName() { return name; }}
Rethinking our model
Using the type system to model nullable value
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
Person
Optional
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
Person
Optional
flatMap(person -> person.getCar())
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
Optional
flatMap(person -> person.getCar())Optional
Car
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
Optional
flatMap(car -> car.getInsurance())Car
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
Optional
flatMap(car -> car.getInsurance())Optional
Insurance
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
Optional
map(insurance -> insurance.getName())Insurance
String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}
Restoring the sanity
Optional
orElse("Unknown")String
Example 10Using Optional
Thinking in FunctionsLearning a new language is relatively easy compared with learning a new paradigm.
Functional Programming is more a new way of thinking than a new tool set
Example 11The Loan Pattern
Example 12Conditional deferred
execution
Futures, weren't they enough?The Future interface was introduced in Java 5 to model an asynchronous computation and then provide an handle to a result that will be made available at some point in the future.
But it doesn't allow to:
✗ Combining two asynchronous computations in one
✗ Waiting for the completion of all tasks performed by a set of Futures
✗ Waiting for the completion of only the quickest task in a set of Futures (possibly because they’re trying to calculate the same value in different ways) and retrieving its result
✗ Programmatically completing a Future
✗ Reacting to a Future completion
CompletableFutures to the rescue✔ Programmatically completing a Future with a result …boolean complete(T value) … or an errorboolean completeExceptionally(Throwable ex)
✔ Reacting to Future completionCompletableFuture<Void> thenAccept(Consumer<? super T> action)
✔ Combining 2 async operation in sequence ...static CompletableFuture<U> thenCompose( Function<? super T,? extends CompletionStage<U>> fn) … or in parallelstatic CompletableFuture<V> thenCombine( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
✔ Waiting for the completion of all Futures ...static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) … or only the fastest onestatic CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
Example 13Putting CompletableFutures
at work
Key TakeawaysThink in functions Strive for immutabilityConfine side-effectsAvoid blocking codeCede control with higher-order functionsLeverage referential transparencyUse FP to design more composable and reusable APIModel potentially missing values with Optionals
… but there are no dogmas
Be pragmatic and use the right tool for the job at hand
Poly-paradigm programming is more powerful and effective than polyglot programming
The bottom line
Java is getting functional
EMBRACE IT!
Mario FuscoRed Hat – Senior Software Engineer
[email protected]: @mariofusco
Q A
Thanks … Questions?
code examples @ https://github.com/mariofusco/Java8WS