Top Banner
Java 8 Functional features 2015-11-09 Rafał Rybacki
45

Java 8 - functional features

Feb 13, 2017

Download

Software

Rafal Rybacki
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Java 8 - functional features

Java 8Functional features

2015-11-09 Rafał Rybacki

Page 2: Java 8 - functional features

Agenda

1. functional paradigm2. lambdas3. functional interfaces4. default methods5. streams

a. parallel iterationb. lazy evaluation

6. map & flat map7. maps

Page 3: Java 8 - functional features

Functional paradigm

● Treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

● Roots in lambda calculus○ Alonzo Church developed concept in 1930○ John McCarthy developed Lisp in 1950s

Page 4: Java 8 - functional features

Functional concepts

● Referential transparency○ Expression can be replaced by the value it returns

● Pure function○ The result depends only on input value, not state○ Evaluation does not cause side-effects

● Idempotence○ Operation with side effects that can be applied multiple time without changing the result

beyond initial application.○ ex. HTTP GET, PUT, DELETED (without POST)

● Higher order function○ Function that takes another function as argument

Page 5: Java 8 - functional features

Functional concepts (2)

● Map○ Apply function to collection items

● Reduce○ Combine input collection into single value result

● Currying○ Translation of function with multiple arguments into higher-order functions

Page 6: Java 8 - functional features

Declarative vs imperative

● Imperative - instructions that changes state○ Assembler○ C

● Declarative - describe what you want○ HTML○ AngularJs (directives)

Page 7: Java 8 - functional features

Declarative vs imperative

Imperative Declarative

getRemoteData("example.com", { data, error in if error == nil { parseData(data, { parsed, error in if error == nil { handleParsedData(parsed, { error in if error != nil { displayError(error) } }) } else { displayError(error) } }) } else { displayError(error) }}

getRemoteData("example.com") .then(parseData) .then(handleParsedData) .onError(displayError)

Page 8: Java 8 - functional features

Declarative vs imperative

● Imperative - “Please take the glass, pour the water and pass it to me.”

● Declarative - “I want to drink water.”

Page 9: Java 8 - functional features

Declarative vs imperative

Declarative programming helps in:

● Direct translation of the business model into business logic● Better readability of the business logic● Better scalability for the program in terms of functionality (reusability)● Easies testing - due to better isolation and loose coupling● Less bugs (quality and safety - due to side effects and avoiding state)

Page 10: Java 8 - functional features

Imperative is mainstream

Imperative programming paradigm is the mainstream.

Page 11: Java 8 - functional features

Imperative is mainstream

Imperative programming paradigm is:

● the most popular,● the easiest,● delivers not best outcome in terms of maintenance,

Imperative programming is mainstream because all of us have been taught it while learning - that’s why it’s mainstream.

Page 12: Java 8 - functional features

Difficulties in declarative programming

● It requires separating small responsibilities● It requires good quality, clear unit tests (instead of debugging)● It requires trust in quality of the implementation● It required transition in thinking (learning it)

getRemoteData("example.com") .then(parseData) .then(handleParsedData) .onError(displayError)

Page 13: Java 8 - functional features

Functional - disadvantages

● Performance of more complex algorithms is lower● Sometimes side-effects are required

○ ex. storing in session○ -> functional approach helps to split stateful and stateless parts

● For some algorithms it decreases readibility

Page 14: Java 8 - functional features

Lambdas

Definition: Lambda is anonymous function.

● Function is first-class citizen● Short syntax

String x = “Hello”;Function y = System.out::println;

y.apply(x);

Page 15: Java 8 - functional features

Lambdas

() -> {}

Page 16: Java 8 - functional features

Lambdas

() -> {}

(input) -> {} input -> {}

Page 17: Java 8 - functional features

Lambdas

() -> {}

(input) -> {}

() -> {output}

input -> {}

() -> output () -> {return output;}

Page 18: Java 8 - functional features

Lambdas

() -> {}

(input) -> {}

() -> {output}

(input) -> {output}

input -> {}

() -> output () -> {return output;}

input -> output

Page 19: Java 8 - functional features

Lambdas

() -> {}

(input) -> {}

() -> {output}

(input) -> {output}

input -> {}

() -> output () -> {return output;}

input -> output

(input1, input2) -> output

Page 20: Java 8 - functional features

Lambdas - functional interfaces

Runnable r = () -> {}

Consumer c = (input) -> {}

Supplier s = () -> {output}

Function f = (input) -> {output}

Page 21: Java 8 - functional features

Lambdas - functional interfaces

BiConsumer bc = (input1, input2) -> {}

UnaryOperator negate = integer -> -integer

BinaryOperator add = (int1, int2) -> int1 + int2

Predicate p = input -> boolean

BiPredicate bp = (input1, input2) -> boolean

Page 22: Java 8 - functional features

Lambda under the hood

Anonymous function:

● Created in compiletime● Resides in the same folder as enclosing class

Lambda:

1. On first run code is generated in runtime using invokedynamic 2. invokedynamic is replaced with code equivalent to anonymous class3. Performance of generated code is the same as anonymous class

Page 23: Java 8 - functional features

● Java 8 introduced new methods in interfaces, like:

● Implementation:

Default methods

Iterable.forEach(Consumer<? super T> action)

default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); }}

Page 24: Java 8 - functional features

The goal:

● Allow Oracle to extend collection interfaces● Allow everybody extend interfaces without breaking compatibility.

Is it useful?

● Not very often.● Keep compatibility in case stateless method are added.

Default methods

Page 25: Java 8 - functional features

● Problem known in languages with multiple inheritance (like C++)

Deadly diamond of death

interface A { default void action() { System.out.println("A"); }}

interface B { default void action() { System.out.println("B"); }}

interface AB extends A, B ?

Page 26: Java 8 - functional features

● Problem:

● Solution:

Deadly diamond of death

interface A { default void action() { System.out.println("A"); }}

interface B { default void action() { System.out.println("B"); }}

interface AB extends A, B { @Override void action();}

interface AB extends A, B { @Override default void action() { A.super.action(); }}

or

Page 27: Java 8 - functional features

Streams

● Streams are for operations● Collections are for storing.

Most often, it is required to process operations rather than store data.

Page 28: Java 8 - functional features

Streams - useful methods

● map● filter● distinct● skip, limit● peek

● min, max● count● findAny, findFirst

users.stream().map(...).filter(...).distinct().skip(5).limit(10).peek(item -> System.out.print(item))

.count();

Page 29: Java 8 - functional features

● Heavy operations pipelined:

Streams are lazy

final Stream<Integer> inputs = // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 IntStream.range(0, 10).boxed();

inputs .map(new HeavyOperation("A")) .map(new HeavyOperation("B")) .map(new HeavyOperation("C")) .filter(input -> input == 3) .collect(toList());

Page 30: Java 8 - functional features

● Heavy operation - prints to console

Streams are lazy

class HeavyOperation {

public Integer apply(Integer input) { System.out.println("Heavy operation " + operationName + " for element " + input); return input; }}

Page 31: Java 8 - functional features

● Heavy operations on all items

Streams are lazy

final Stream<Integer> inputs = IntStream.range(0, 10).boxed();

inputs .map(new HeavyOperation("A")) .map(new HeavyOperation("B")) .map(new HeavyOperation("C")) .collect(toList());

Heavy operation A for element 0Heavy operation B for element 0Heavy operation C for element 0Heavy operation A for element 1Heavy operation B for element 1Heavy operation C for element 1Heavy operation A for element 2Heavy operation B for element 2Heavy operation C for element 2Heavy operation A for element 3Heavy operation B for element 3Heavy operation C for element 3Heavy operation A for element 4Heavy operation B for element 4Heavy operation C for element 4Heavy operation A for element 5Heavy operation B for element 5Heavy operation C for element 5Heavy operation A for element 6Heavy operation B for element 6Heavy operation C for element 6Heavy operation A for element 7Heavy operation B for element 7Heavy operation C for element 7Heavy operation A for element 8Heavy operation B for element 8Heavy operation C for element 8Heavy operation A for element 9Heavy operation B for element 9Heavy operation C for element 9

CONSOLE OUTPUT

Page 32: Java 8 - functional features

● Only required operations

Streams are lazy

final Stream<Integer> inputs = IntStream.range(0, 10).boxed();

inputs .map(new HeavyOperation("A")) .map(new HeavyOperation("B")) .map(new HeavyOperation("C")) .filter(input -> input == 3) .findFirst();

Heavy operation A for element 0Heavy operation B for element 0Heavy operation C for element 0Heavy operation A for element 1Heavy operation B for element 1Heavy operation C for element 1Heavy operation A for element 2Heavy operation B for element 2Heavy operation C for element 2Heavy operation A for element 3Heavy operation B for element 3Heavy operation C for element 3

CONSOLE OUTPUT

Page 33: Java 8 - functional features

● No collection operation

Streams are lazy

final Stream<Integer> inputs = IntStream.range(0, 10).boxed();

inputs .map(new HeavyOperation("A")) .map(new HeavyOperation("B")) .map(new HeavyOperation("C")) .filter(input -> input == 3);

CONSOLE OUTPUT

Page 34: Java 8 - functional features

● Collection function

● Reduction function (fold function)

list .stream() .collect(toList());

Streams - operations output

int sum = integers .stream() .sum();

int sum = integers .stream() .reduce(0, (total, item) -> total + item);

Page 35: Java 8 - functional features

Streams - parallel execution

List inputs = ...

inputs .parallelStream() .map(new HeavyOperation("A")) .map(new HeavyOperation("B")) .map(new HeavyOperation("C")) .findFirst();

Page 36: Java 8 - functional features

Streams - collectors

● Aggregating

● Comparing

● Grouping

List<User> allUsers = users.stream().collect(toList());

User userWithMaxLogins = users.stream() .collect( maxBy( comparing(User::loginsCount) ) );

Map<Role, List<User>> usersPerRole = users.stream() .collect( groupingBy( User::getRole) );

Page 37: Java 8 - functional features

Streams - collectors

● Partitioning

Map<Boolean, List<User>> activeUsers = stream .collect( partitioningBy( user -> user.getLoginsCount() > 0));

Page 38: Java 8 - functional features

Flat mapping

● Stream: Combine stream of streams into single stream.

● Optional: Combine optional of optional into single optional.○ A pipeline of operations out of which any may fail

Page 39: Java 8 - functional features

Flat mapping

private Optional<Banana> fetchBananaImperative() { Optional<Island> island = findIsland();

boolean noIslandFound = !island.isPresent(); if (noIslandFound) { return empty(); }

Optional<Jungle> jungle = findJungle(island.get());

boolean noJungleFound = !jungle.isPresent(); if (noJungleFound) { return empty(); }

Optional<Tree> tree = findTree(jungle.get());

boolean noTreeFound = !tree.isPresent(); if (noTreeFound) { return empty(); }

Optional<Banana> banana = findBanana(tree.get());

boolean noBananaFound = !banana.isPresent(); if (noBananaFound) { return empty(); }

return banana; }

Page 40: Java 8 - functional features

Flat mapping

private Optional<Banana> fetchBananaImperative() { Optional<Island> island = findIsland();

boolean noIslandFound = !island.isPresent(); if (noIslandFound) { return empty(); }

Optional<Jungle> jungle = findJungle(island.get());

boolean noJungleFound = !jungle.isPresent(); if (noJungleFound) { return empty(); }

Optional<Tree> tree = findTree(jungle.get());

boolean noTreeFound = !tree.isPresent(); if (noTreeFound) { return empty(); }

Optional<Banana> banana = findBanana(tree.get());

boolean noBananaFound = !banana.isPresent(); if (noBananaFound) { return empty(); }

return banana; }

private Optional<Banana> fetchBananaFluent() { Optional<Island> islandOptional = findIsland();

Optional<Banana> banana = islandOptional .flatMap(island -> findJungle(island)) .flatMap(jungle -> findTree(jungle)) .flatMap(tree -> findBanana(tree));

return banana;}

Page 41: Java 8 - functional features

Map - new methods

map.compute(key, (key, value) -> newValue);

map.putIfAbsent(key, value);

map.replace(key, value);

map.replace(key, oldValue, newValue);

map.getOrDefault(key, defaultValue);

map.merge(key, newValue, (oldValue, newValue) -> mergedValue);

Page 42: Java 8 - functional features

Lambda vs method reference

Optional<Banana> banana = islandOptional .flatMap(island -> findJungle(island)) .flatMap(jungle -> findTree(jungle)) .flatMap(tree -> findBanana(tree));

Page 43: Java 8 - functional features

Lambda vs method reference

Optional<Banana> banana = islandOptional .flatMap(island -> findJungle(island)) .flatMap(jungle -> findTree(jungle)) .flatMap(tree -> findBanana(tree));

Optional<Banana> banana = islandOptional .flatMap(island -> findJungle(island)) .flatMap(Jungle::getTree) .flatMap(tree -> tree.getBanana());

● Syntax may affect readibility

Page 44: Java 8 - functional features

Lambda vs method reference

Options:

● allow mixing lambda and method reference● only lambdas

Optional<Banana> banana = islandOptional .flatMap(island -> findJungle(island)) .flatMap(Jungle::getTree) .flatMap(tree -> tree.getBanana());

Page 45: Java 8 - functional features

Thanks.