java 8 lambda expressions

66
Java 8 Lambda Expressions Logan Chien

Upload: logan-chien

Post on 13-Apr-2017

1.034 views

Category:

Software


1 download

TRANSCRIPT

Java 8 Lambda Expressions

Logan Chien

2

Agenda

● Lambda Expression and Closure

● Stream and Operations

● Parallelism

● Functional Interface

3

Introduction

4

Background

Before Java 8, there were anonymous classes:

JButton btn = new JButton("Click");

btn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent evt) {

System.out.println("Button clicked!");

}

});

5

Lambda Expressions

It is cumbersome to write anonymous classes in

many cases. Thus, lambda expressions are

introduced:

JButton btn = new JButton("Click");

btn.addActionListener(

evt -> System.out.println("Button clicked!"));

6

Lambda Syntax● No arguments:

● One argument:

● Two arguments:

● With explicit argument types:

● Multiple statements:

() -> System.out.println("Hello")

s -> System.out.println(s)

(x, y) -> x + y

(Integer x, Integer y) -> x + y

(x, y) -> {

System.out.println(x);

System.out.println(y);

return (x + y);

}

7

Lambda Syntax Sugar 1/2

Method reference

public class HashFunction {

public static int compute1(Object obj) { /*...*/ }

public static int compute2(int a, int b) { /*...*/ }

}

HashFunc::compute1

(x) -> HashFunc::compute1(x)

HashFunc::compute2

(x, y) -> HashFunc::compute2(x, y)

1

2

8

Lambda Syntax Sugar 2/2

Constructor reference

String::new () -> new String()

(x) -> new String(x)

(x, y) -> new String(x, y)

(x, y, z) -> new String(x, y, z)

int[]::new (s) -> new int[s]

1

2

9

Lambda Expression vs. Closure● Lambda expression

– A piece of the source code have the semantics like

anonymous classes.

– Evaluated to a closure object in the run-time.

● Closure

– The run-time object that contains the captured

variables and an associated method.

10

Capture Variable 1/3

● We can refer to local variables in lambda

expressions.

– The referred local variables should be either final

or effectively final.

● Evaluation of lambda expression will copy the

referred local variables to closure objects.

11

Capture Variable 2/3import java.util.stream.*;

import java.util.function.*;

public class CaptureExample {

public static IntUnaryOperator addBy(int n) {

return (x) -> x + n;

}

public static void main(String[] args) {

IntStream.of(1, 2, 3).map(addBy(1))

.map(addBy(2))

.forEach(System.out::println);

}

}

Capture variable11

2 The lambda expression is evaluated twice. Thus, twoclosure objects are returned.

12

Capture Variable 3/3import java.util.function.*;

public class NonEffectivelyFinalExample {

public static IntUnaryOperator foo(int a, int n) {

if (a < 0) {

return (x) -> x + n;

}

n -= a;

return (x) -> x + n;

}

}

NonEffectivelyFinalExample.java:5: error: local variables referenced from

a lambda expression must be final or effectively final

return (x) -> x + n;

^

NonEffectivelyFinalExample.java:8: error: local variables referenced from

a lambda expression must be final or effectively final

return (x) -> x + n;

^

2 errors

Not effectively final

13

Type Inference

What is the type for following lambda

expression?

Ans: It depends on the context. Java compiler

will infer the type automatically.

(x) -> System.out.println(x)

IntConsumer f = (x) -> System.out.println(x);

// (int) -> void

DoubleConsumer g = (x) -> System.out.println(x);

// (double) -> void

14

Questions?

15

Stream & Operations

16

Big Picture

How to Use Lambdas?● Stream – Create a stream from the collections,

arrays, etc.

● Lazy operations – Operations that will be applied

to stream elements on demand.

● Eager operations – Operations that will trigger the evaluations.

– Enumerators, Collectors, etc.

17

Stream

● Stream is an abstraction of the underlying

data structure.

● Isolates the data processing and control flow.

● Abstracts the intermediate data structures.

● Abstracts the lazy evaluation of operations.

18

How to Create Stream?● From programmer-specified elements

● From arrays

● From java.util.Collection

● From java.util.Map

java.util.Stream.of(...)

java.util.Arrays.stream(array)

java.util.Collection.stream()

java.util.Collection.parallelStream()

java.util.Map.entrySet().stream()

java.util.Map.values().stream()

19

Operations

● Lazy evaluation– filter

– map

– flatMap

– peek

– ... etc

● Eager evaluation– count

– forEach

– toArray

– reduce

– collect

– ... etc

Tips: Check the return type. The lazy operations will return Stream.

20

Lazy Operations

21

filter() 1/2

● filter() takes a predicate function

● If the predicate function returns false, then

the element will be removed from the

stream.1 2 3 4 5

1 3 5

Input Stream

Output Stream

filter(x -> x % 2 != 0)

22

filter() 2/2import java.util.stream.*;

public class FilterExample {

public static void main(String[] args) {

Stream.of(1, 2, 3, 4, 5)

.filter(x -> x % 2 != 0)

.forEach(System.out::println);

}

}1 2 3 4 5

1 3 5

Input Stream

Output Stream

filter(x -> x % 2 != 0)

23

map() 1/2

● map() takes an unary function which will

convert one element to another element.

1 3 5Input Stream

Output Stream

…S:0 S:1 S:2

map(x -> "S:" + x / 2)

24

import java.util.stream.*;

public class MapExample {

public static void main(String[] args) {

Stream.of(1, 3, 5)

.map(x -> "S:" + x / 2)

.forEach(System.out::println);

}

}

map() 2/2

1 3 5Input Stream

Output Stream

…S:0 S:1 S:2

map(x -> "S:" + x / 2)

Integer

String

25

flatMap() 1/4● flatMap() takes an unary function.

● The unary function will take an element and

return a stream.

● flatMap() will concatenate these streams.

1 2 3Input Stream

Output Stream

…0 0 1 0 1 2

flatMap(x -> IntStream.range(0, x).boxed())

26

flatMap() 2/4import java.util.stream.*;

public class FlatMapExample1 {

public static void main(String[] args) {

Stream.of(1, 2, 3)

.flatMap(x -> IntStream.range(0, x).boxed())

.forEach(System.out::println);

}

}1 2 3Input Stream

Output Stream

…0 0 1 0 1 2

flatMap(x -> IntStream.range(0, x).boxed())

27

flatMap() 3/4

A

B

C

a

b bb bbb

c cc

HashMap<String, String[]>

Key Value

28

flatMap() 4/4import java.util.*;

import java.util.stream.*;

public class FlatMapExample2 {

public static void main(String[] args) {

HashMap<String, String[]> m =

new HashMap<String, String[]>();

m.put("A", new String[]{"a"});

m.put("B", new String[]{"b", "bb", "bbb"});

m.put("C", new String[]{"c", "cc"});

m.entrySet().stream()

.flatMap(e -> Arrays.stream(e.getValue()))

.forEach(System.out::println);

}

}

29

peek() 1/3● peek() is the debug routine.

– e.g. Print the value during the lazy evaluation.

● It will call the closure method with an element.

– You can print the element.

– You can manipulate the object (but not

recommended.)

30

peek() 2/3import java.util.stream.*;

public class PeekExample1 {

public static void main(String[] args) {

Stream.of(1, 2, 3)

.map(x -> x * 2)

.peek(x -> System.out.println("a:" + x))

.map(x -> x + 1)

.peek(x -> System.out.println("b:" + x))

.forEach(System.out::println);

}

};

a:2

b:3

3

a:4

b:5

5

a:6

b:7

7

Output

31

peek() 3/3import java.util.stream.*;

class Num {

private int value;

public Num(int i) { value = i; }

public int get() { return value; }

public void set(int i) { value = i; }

};

public class PeekExample2 {

public static void main(String[] args) {

Stream.of(new Num(1), new Num(2), new Num(3))

.peek(x -> x.set(5))

.forEach(x -> System.out.println(x.get()));

}

};

5

5

5

Output

32

Remarks 1/2● Lazy operations won't be executed until they

are triggered by an eager operation.

– This example will not print any numbers.

– The lambda expression is evaluated.

– The method in the closure is NOT invoked.Stream.of(1, 2, 3).filter(x -> {

System.out.println(x);

return true;

});

33

Remarks 2/2● It is suggested to use pure functions (i.e. no

side-effects) for these operations.

– It is hard to predict the behavior when we have

both mutability and lazy evaluation.

– Mutable + Lazy Evaluation = Disaster

– Except: For debug purpose only.

34

Eager Operations

35

forEach()

forEach() will iterate through the stream and

invoke the closure method with each element.

Stream.of(1, 2, 3, 4, 5)

.forEach(x -> System.out.println("GOT:" + x));

36

count()

count() counts the number of elements in a

stream.import java.util.stream.*;

public class CountExample {

public static boolean isPrime(int i) {

if (i > 2 && i % 2 == 0) return false;

for (int p = 3; p * p <= i; p += 2)

if (i % p == 0) return false;

return true;

}

public static void main(String[] args) {

long num = IntStream.range(2, 100).filter(Count::isPrime).count();

System.out.println("Num primes in [2,100) = " + num);

}

}

37

toArray()

toArray() collects all of the elements from a

stream and put them in a java.lang.Object

array. import java.util.stream.*;

public class ToArrayExample {

public static void main(String[] args) {

Object[] array = Stream.of("apple", "bear", "bat", "cat")

.filter(x -> x.startsWith("b")).toArray();

for (Object s : array) {

System.out.println((String)s);

}

}

}

38

reduce() 1/4

● There are three overloaded methods

● Accumulator should be associative.

1

2

3

Optional<T> reduce(BinaryOperator<T> acc)

T reduce(T id, BinaryOperator<T> acc)

<U> U reduce(U id, BiFunction<U, ? super T, U> acc,

BinaryOperator<U> combiner)

39

reduce() 2/4

import java.util.Optional;

import java.util.stream.*;

public class ReduceExample1 {

public static Optional<Integer> sum(Stream<Integer> s) {

return s.reduce((x, y) -> x + y);

}

public static void main(String[] args) {

System.out.println(sum(Stream.of()));

System.out.println(sum(Stream.of(1)));

System.out.println(sum(Stream.of(1, 2)));

}

}

OutputOptional.empty

Optional[1]

Optional[3]

40

reduce() 3/4

import java.util.Optional;

import java.util.stream.*;

public class ReduceExample2 {

public static int sum(Stream<Integer> s) {

return s.reduce(0, (x, y) -> x + y);

}

public static void main(String[] args) {

System.out.println(sum(Stream.of()));

System.out.println(sum(Stream.of(1)));

System.out.println(sum(Stream.of(1, 2)));

}

}

Output0

1

3

41

collect()

● collect() takes a collector object and send

each element to the collector.

● It allows the run-time to build the final data

structure gradually.

42

Common Collectors

● Collectors.toList()

● Collectors.toSet()

● Colloctors.groupingBy()

● Collectors.joining()

● Find more in java.util.stream.Collectors.

43

collect(toList())import java.util.List;

import java.util.stream.*;

public class CollectToListExample {

public static void main(String[] args) {

List<Integer> odds = Stream.of(1, 2, 3, 4, 5)

.filter(x -> x % 2 != 0)

.collect(Collectors.toList());

for (Integer i : odds) {

System.out.println(i);

}

}

}

44

collect(toSet())import java.util.Set;

import java.util.stream.*;

public class CollectToSetExample {

public static void main(String[] args) {

Set<Integer> congs = Stream.of(1, 2, 3, 4, 5)

.map(x -> x % 2)

.collect(Collectors.toSet());

for (Integer i : congs) {

System.out.println(i);

}

}

}

45

collect(groupingBy())import java.util.*;

import java.util.stream.*;

public class CollectGroupingByExample {

public static void main(String[] args) {

Map<String, List<Integer>> groups =

Stream.of(1, 2, 3, 4, 5)

.collect(Collectors.groupingBy(

x -> x % 2 == 0 ? "even" : "odd"));

for (Map.Entry<String, List<Integer>> e : groups.entrySet()) {

System.out.println(e.getKey() + ":" + e.getValue());

}

}

}

46

collect(joining())import java.util.stream.*;

public class CollectJoinExample {

public static void main(String[] args) {

String s = Stream.of(1, 2, 3, 4, 5)

.map(x -> x.toString())

.collect(Collectors.joining(", "));

System.out.println(s);

}

}

Output

1, 2, 3, 4, 5

47

Specialized Types

48

Specialized Types● To reduce box/unbox overhead, several

specialized types for int, long, and double are

provided.

– Streams

– Function Interfaces

– Predicate

– Collector

49

Specialized Stream

● 3 specialized (unboxed) streams:

– DoubleStream, IntStream, and LongStream

● Box streams (convert from TypeStream to Stream<T>)

– Call IntStream.boxed() method.

● Unbox streams (convert from Stream<T> to IntStream)

– Call Stream<T>.mapToInt() method.

50

Specialized Functions● One argument and one return value

– Function<T, R>

– IntFunction<R>

– ToIntFunction<T>

– LongToIntFunction

– UnaryOperator<T>

– IntUnaryOperator

R apply(T t)

R apply(int t)

int applyAsInt(T t)

R apply(R t)

int applyAsInt(int t)

int applyAsInt(long t)

51

Specialized Functions

● Two arguments and one return value

– BiFunction<T, U, R>

– ToIntBiFunction<T, U>

– BinaryOperator<T>

– IntBinaryOperator

R apply(T t, U u)

int applyAsInt(T t, U u)

R apply(T t, U u)

int applyAsInt(int t, int u)

52

Specialized Predicate

● Specialized stream requires specialized

predicates.

– allMatch, anyMatch, filter, noneMatch

● Comparison

– Predicate<T>

– IntPredicate

boolean test(T t)

boolean test(int t)

53

Specialized Collectors Methods

● Average

● Summerizing

● Summing

Collector<T, ?, Double>

averagingInt(ToIntFunction<? super T> mapper)

Collector<T, ?, IntSummaryStatistics>

summerizingInt(ToIntFunction<? super T> mapper)

Collector<T, ?, Integer>

summingInt(ToIntFunction<? super T> mapper)

54

Questions?

55

Parallelism

56

Parallelism

● If we limit ourselves to this restricted

programming style, then we can (almost) get

parallelism for free.

● Call parallel() to convert a stream to

parallel stream.

57

Parallelism Examplepublic class ParallelExample {

public static long sumSeq(long[] array) {

return Arrays.stream(array).sum();

}

public static long sumPar(long[] array) {

return Arrays.stream(array).parallel().sum();

}

public static void main(String[] args) {

int size = 40000000;

long[] array = new long[size];

for (int i = 0; i < size; ++i) array[i] = i;

long startSeq = System.nanoTime();

long resSeq = sumSeq(array);

long durationSeq = System.nanoTime() - startSeq;

long startPar = System.nanoTime();

long resPar = sumPar(array);

long durationPar = System.nanoTime() - startPar;

}

}

Output

resultSeq: 799999980000000

resultPar: 799999980000000

durationSeq: 58598502

durationPar: 21206305

58

Questions?

59

Functional Interface

60

How to Receive Lambdas?● The closure object is an object that can be passed around.

● The closure class will implement the specified interface.

● To run the body of the lambda expression, invoke the

corresponding method.

● The interface should contain exactly one non-default

abstract method.

– Because we can choose arbitrary method name.

61

Functional Interfaces

● Interfaces with one non-default method.

● It is suggested to annotate with:

@FunctionalInterface.@FunctionalInterface

public interface MouseEventListener {

public void accept(MouseEvent evt);

}

panel.registerListener(

evt -> System.out.println("Processed!"));

62

Functional Interfacesimport java.util.HashSet;

public class Panel {

private HashSet<MouseEventListener> listeners =

new HashSet<MouseEventListener>();

public void registerListener(MouseEventListener cb) {

listeners.add(cb);

}

public void notifyListener(MouseEvent evt) {

listeners.forEach(x -> x.accept(evt));

}

}

63

Default Method● Problem: The language designer would like to

add functions to interfaces, but it will break

the existing program.

● Solution: We can specify the default method in

an interface.

– Default methods should only use other abstract

methods, i.e. no field access is allowed.

64

Default Method● Default methods will be inherited (and may be

overrided) when the interface is extended.

● The closest default method will be chosen.

● To invoke the super default method, use the

super keyword.

● It is necessary to override one if there are conflicts.

65

Static Method

● In Java 8, we can write static methods in

interfaces as well.

● For example, Stream.of() is a notable static

method in the interface.

66

Thanks!

Any questions?