Java 8 Workshop

Post on 11-Jul-2015

2.266 views 5 download

Transcript of Java 8 Workshop

by Mario Fuscomario.fusco@gmail.comtwitter: @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

mario.fusco@gmail.comtwitter: @mariofusco

Q A

Thanks … Questions?

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