Java 8
Functional features
2015-11-09 Rafał Rybacki
Agenda
1. functional paradigm
2. lambdas
3. functional interfaces
4. default methods
5. streams
a. parallel iteration
b. lazy evaluation
6. map & flat map
7. maps
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
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
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
Declarative vs imperative
● Imperative - instructions that changes state
○ Assembler
○ C
● Declarative - describe what you want
○ HTML
○ AngularJs (directives)
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)
Declarative vs imperative
● Imperative - “Please take the glass, pour the water and pass it to me.”
● Declarative - “I want to drink water.”
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)
Imperative is mainstream
Imperative programming paradigm is the mainstream.
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.
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)
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
Lambdas
Definition: Lambda is anonymous function.
● Function is first-class citizen
● Short syntax
String x = “Hello”;
Function y = System.out::println;
y.apply(x);
Lambdas
() -> {}
Lambdas
() -> {}
(input) -> {} input -> {}
Lambdas
() -> {}
(input) -> {}
() -> {output}
input -> {}
() -> output () -> {return output;}
Lambdas
() -> {}
(input) -> {}
() -> {output}
(input) -> {output}
input -> {}
() -> output () -> {return output;}
input -> output
Lambdas
() -> {}
(input) -> {}
() -> {output}
(input) -> {output}
input -> {}
() -> output () -> {return output;}
input -> output
(input1, input2) -> output
Lambdas - functional interfaces
Runnable r = () -> {}
Consumer c = (input) -> {}
Supplier s = () -> {output}
Function f = (input) -> {output}
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
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 class
3. Performance of generated code is the same as anonymous class
● 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);
}
}
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
● 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 ?
● 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
Streams
● Streams are for operations
● Collections are for storing.
Most often, it is required to process operations rather than store data.
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();
● 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());
● 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;
}
}
● 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 0
Heavy operation B for element 0
Heavy operation C for element 0
Heavy operation A for element 1
Heavy operation B for element 1
Heavy operation C for element 1
Heavy operation A for element 2
Heavy operation B for element 2
Heavy operation C for element 2
Heavy operation A for element 3
Heavy operation B for element 3
Heavy operation C for element 3
Heavy operation A for element 4
Heavy operation B for element 4
Heavy operation C for element 4
Heavy operation A for element 5
Heavy operation B for element 5
Heavy operation C for element 5
Heavy operation A for element 6
Heavy operation B for element 6
Heavy operation C for element 6
Heavy operation A for element 7
Heavy operation B for element 7
Heavy operation C for element 7
Heavy operation A for element 8
Heavy operation B for element 8
Heavy operation C for element 8
Heavy operation A for element 9
Heavy operation B for element 9
Heavy operation C for element 9
CONSOLE OUTPUT
● 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 0
Heavy operation B for element 0
Heavy operation C for element 0
Heavy operation A for element 1
Heavy operation B for element 1
Heavy operation C for element 1
Heavy operation A for element 2
Heavy operation B for element 2
Heavy operation C for element 2
Heavy operation A for element 3
Heavy operation B for element 3
Heavy operation C for element 3
CONSOLE OUTPUT
● 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
● 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);
Streams - parallel execution
List inputs = ...
inputs
.parallelStream()
.map(new HeavyOperation("A"))
.map(new HeavyOperation("B"))
.map(new HeavyOperation("C"))
.findFirst();
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) );
Streams - collectors
● Partitioning
Map<Boolean, List<User>> activeUsers = stream
.collect(
partitioningBy(
user -> user.getLoginsCount() > 0));
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
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;
}
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;
}
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);
Lambda vs method reference
Optional<Banana> banana = islandOptional
.flatMap(island -> findJungle(island))
.flatMap(jungle -> findTree(jungle))
.flatMap(tree -> findBanana(tree));
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
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());
Thanks.

Java 8 - functional features

  • 1.
  • 2.
    Agenda 1. functional paradigm 2.lambdas 3. functional interfaces 4. default methods 5. streams a. parallel iteration b. lazy evaluation 6. map & flat map 7. maps
  • 3.
    Functional paradigm ● Treatscomputation 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
  • 4.
    Functional concepts ● Referentialtransparency ○ 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
  • 5.
    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
  • 6.
    Declarative vs imperative ●Imperative - instructions that changes state ○ Assembler ○ C ● Declarative - describe what you want ○ HTML ○ AngularJs (directives)
  • 7.
    Declarative vs imperative ImperativeDeclarative 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)
  • 8.
    Declarative vs imperative ●Imperative - “Please take the glass, pour the water and pass it to me.” ● Declarative - “I want to drink water.”
  • 9.
    Declarative vs imperative Declarativeprogramming 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)
  • 10.
    Imperative is mainstream Imperativeprogramming paradigm is the mainstream.
  • 11.
    Imperative is mainstream Imperativeprogramming 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.
  • 12.
    Difficulties in declarativeprogramming ● 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)
  • 13.
    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
  • 14.
    Lambdas Definition: Lambda isanonymous function. ● Function is first-class citizen ● Short syntax String x = “Hello”; Function y = System.out::println; y.apply(x);
  • 15.
  • 16.
    Lambdas () -> {} (input)-> {} input -> {}
  • 17.
    Lambdas () -> {} (input)-> {} () -> {output} input -> {} () -> output () -> {return output;}
  • 18.
    Lambdas () -> {} (input)-> {} () -> {output} (input) -> {output} input -> {} () -> output () -> {return output;} input -> output
  • 19.
    Lambdas () -> {} (input)-> {} () -> {output} (input) -> {output} input -> {} () -> output () -> {return output;} input -> output (input1, input2) -> output
  • 20.
    Lambdas - functionalinterfaces Runnable r = () -> {} Consumer c = (input) -> {} Supplier s = () -> {output} Function f = (input) -> {output}
  • 21.
    Lambdas - functionalinterfaces BiConsumer bc = (input1, input2) -> {} UnaryOperator negate = integer -> -integer BinaryOperator add = (int1, int2) -> int1 + int2 Predicate p = input -> boolean BiPredicate bp = (input1, input2) -> boolean
  • 22.
    Lambda under thehood 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 class 3. Performance of generated code is the same as anonymous class
  • 23.
    ● Java 8introduced 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); } }
  • 24.
    The goal: ● AllowOracle 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
  • 25.
    ● Problem knownin 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 ?
  • 26.
    ● Problem: ● Solution: Deadlydiamond 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
  • 27.
    Streams ● Streams arefor operations ● Collections are for storing. Most often, it is required to process operations rather than store data.
  • 28.
    Streams - usefulmethods ● 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();
  • 29.
    ● Heavy operationspipelined: 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());
  • 30.
    ● 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; } }
  • 31.
    ● Heavy operationson 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 0 Heavy operation B for element 0 Heavy operation C for element 0 Heavy operation A for element 1 Heavy operation B for element 1 Heavy operation C for element 1 Heavy operation A for element 2 Heavy operation B for element 2 Heavy operation C for element 2 Heavy operation A for element 3 Heavy operation B for element 3 Heavy operation C for element 3 Heavy operation A for element 4 Heavy operation B for element 4 Heavy operation C for element 4 Heavy operation A for element 5 Heavy operation B for element 5 Heavy operation C for element 5 Heavy operation A for element 6 Heavy operation B for element 6 Heavy operation C for element 6 Heavy operation A for element 7 Heavy operation B for element 7 Heavy operation C for element 7 Heavy operation A for element 8 Heavy operation B for element 8 Heavy operation C for element 8 Heavy operation A for element 9 Heavy operation B for element 9 Heavy operation C for element 9 CONSOLE OUTPUT
  • 32.
    ● Only requiredoperations 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 0 Heavy operation B for element 0 Heavy operation C for element 0 Heavy operation A for element 1 Heavy operation B for element 1 Heavy operation C for element 1 Heavy operation A for element 2 Heavy operation B for element 2 Heavy operation C for element 2 Heavy operation A for element 3 Heavy operation B for element 3 Heavy operation C for element 3 CONSOLE OUTPUT
  • 33.
    ● No collectionoperation 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
  • 34.
    ● 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);
  • 35.
    Streams - parallelexecution List inputs = ... inputs .parallelStream() .map(new HeavyOperation("A")) .map(new HeavyOperation("B")) .map(new HeavyOperation("C")) .findFirst();
  • 36.
    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) );
  • 37.
    Streams - collectors ●Partitioning Map<Boolean, List<User>> activeUsers = stream .collect( partitioningBy( user -> user.getLoginsCount() > 0));
  • 38.
    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
  • 39.
    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; }
  • 40.
    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; }
  • 41.
    Map - newmethods 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);
  • 42.
    Lambda vs methodreference Optional<Banana> banana = islandOptional .flatMap(island -> findJungle(island)) .flatMap(jungle -> findTree(jungle)) .flatMap(tree -> findBanana(tree));
  • 43.
    Lambda vs methodreference 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
  • 44.
    Lambda vs methodreference Options: ● allow mixing lambda and method reference ● only lambdas Optional<Banana> banana = islandOptional .flatMap(island -> findJungle(island)) .flatMap(Jungle::getTree) .flatMap(tree -> tree.getBanana());
  • 45.