Java 8 Workshop

85
by Mario Fusco [email protected] twitter: @mariofusco 8 in Acon Lambdas, Streams, and funconal-style programming

Transcript of Java 8 Workshop

Page 1: Java 8 Workshop

by Mario [email protected]: @mariofusco

8 in ActionLambdas, Streams, and functional-style programming

Page 2: Java 8 Workshop

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;}

Page 3: Java 8 Workshop

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

Page 4: Java 8 Workshop

… 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

Page 5: Java 8 Workshop

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

Page 6: Java 8 Workshop

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

Page 7: Java 8 Workshop

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

Page 8: Java 8 Workshop

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

Page 9: Java 8 Workshop

Example 1Your first Lambda

Page 10: Java 8 Workshop

Internal vs External Iteration

Page 11: Java 8 Workshop

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

Page 12: Java 8 Workshop

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());

Page 13: Java 8 Workshop

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

Page 14: Java 8 Workshop

Example 2Passing behavior

with Lambda

Page 15: Java 8 Workshop

OOP makes code understandable by encapsulating moving parts

FP makes code understandable by minimizing moving parts

- Michael Feathers

OOP vs FP

Page 16: Java 8 Workshop

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

Page 17: Java 8 Workshop

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

Page 18: Java 8 Workshop

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);

Page 19: Java 8 Workshop

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)

Page 20: Java 8 Workshop

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(); }}

Page 21: Java 8 Workshop

Example 3Stream laziness

Page 22: Java 8 Workshop

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

Page 23: Java 8 Workshop

map : an intermediate operation

map( -> )

Stream

Page 24: Java 8 Workshop

reduce : a terminal operation

Stream

Integerreduce(0, (a, b) -> a + b)

Page 25: Java 8 Workshop

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);

Page 26: Java 8 Workshop

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

Page 27: Java 8 Workshop

Example 4Prime numbers

Page 28: Java 8 Workshop

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();}

Page 29: Java 8 Workshop

Example 5Stream from a File

Page 30: Java 8 Workshop

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); }

Page 31: Java 8 Workshop

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));

Page 32: Java 8 Workshop

Example 6Grouping

Page 33: Java 8 Workshop

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

Page 34: Java 8 Workshop

employees.stream() .filter(e -> e.getRole() == Role.MANAGER) .map(Employee::getIncome) .reduce(0, (a, b) -> a + b);

parallelStream()

Streams – Parallelism for free

Page 35: Java 8 Workshop

A parallel reduction

Page 36: Java 8 Workshop

Example 7Parallel Streams

Page 37: Java 8 Workshop

Is there such thing as a free lunch?

Page 38: Java 8 Workshop

Probably yes …

… but we need functional forks and knives to eat it

Page 39: Java 8 Workshop

Concurrency & Parallelism

Parallel programmingRunning multiple tasks at

the same time

Concurrent programmingManaging concurrent requests

Both are hard!

Page 40: Java 8 Workshop

The cause of the problem …

Mutable state +Parallel processing =Non-determinism

FunctionalProgramming

Page 41: Java 8 Workshop

Too hard to think about them!

Race conditions

Deadlocks

Starvation

Livelocks

… and its effects

Page 42: Java 8 Workshop

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

Page 43: Java 8 Workshop

Different concurrency models

Isolated mutable state (actors)

Purely immutable (pure functions)

Shared mutable state (threads + locks)

Page 44: Java 8 Workshop

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); } }}

Page 45: Java 8 Workshop

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); }}

Page 46: Java 8 Workshop

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); }}

Page 47: Java 8 Workshop

The state quadrantsMutable

Immutable

Shared

Unshared

Actors

FunctionalProgramming

Threads

Determinism

Non-determinism

Page 48: Java 8 Workshop

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

Page 49: Java 8 Workshop

Example 8Modularity to

confine side-effects

Page 50: Java 8 Workshop

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.

Page 51: Java 8 Workshop

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

Page 52: Java 8 Workshop

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

Page 53: Java 8 Workshop

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.

Page 54: Java 8 Workshop

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)

Page 55: Java 8 Workshop

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

Page 56: Java 8 Workshop

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

Page 57: Java 8 Workshop

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

Page 58: Java 8 Workshop

Example 9Avoiding mutability

Page 59: Java 8 Workshop

NullPointerException

Raise your hand if you've ever seen this

Page 60: Java 8 Workshop

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”

Page 61: Java 8 Workshop

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

Page 62: Java 8 Workshop

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

Page 63: Java 8 Workshop

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

Page 64: Java 8 Workshop

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()}

Page 65: Java 8 Workshop

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); }}

Page 66: Java 8 Workshop

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

Page 67: Java 8 Workshop

String getCarInsuranceName(Optional<Person> person) { return person.flatMap(person -> person.getCar()) .flatMap(car -> car.getInsurance()) .map(insurance -> insurance.getName()) .orElse("Unknown");}

Restoring the sanity

Page 68: Java 8 Workshop

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

Page 69: Java 8 Workshop

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())

Page 70: Java 8 Workshop

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

Page 71: Java 8 Workshop

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

Page 72: Java 8 Workshop

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

Page 73: Java 8 Workshop

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

Page 74: Java 8 Workshop

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

Page 75: Java 8 Workshop

Example 10Using Optional

Page 76: Java 8 Workshop

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

Page 77: Java 8 Workshop

Example 11The Loan Pattern

Page 78: Java 8 Workshop

Example 12Conditional deferred

execution

Page 79: Java 8 Workshop

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

Page 80: Java 8 Workshop

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)

Page 81: Java 8 Workshop

Example 13Putting CompletableFutures

at work

Page 82: Java 8 Workshop

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

Page 83: Java 8 Workshop

The bottom line

Java is getting functional

EMBRACE IT!

Page 84: Java 8 Workshop
Page 85: Java 8 Workshop

Mario FuscoRed Hat – Senior Software Engineer

[email protected]: @mariofusco

Q A

Thanks … Questions?

code examples @ https://github.com/mariofusco/Java8WS