Sei sulla pagina 1di 102

Lambda Expressions

Richard Warburton
James Gough
Raoul-Gabriel Urma
Outline of module
1. Why Java 8?
2. Behaviour Parameterisation
3. What is a lambda?
4. Functional interfaces: where to use
lambdas?
5. Method references
6. Advanced details
Why Java 8?
Why Java 8? (1)

• We write obscure code to do simple things:

Collections.sort(inventory,new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
});
Why Java 8? (1)
Collections.sort(inventory,new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
});

VS

inventory.sort(comparing(Apple::getWeight));
Why Java 8? (2)

• Commodity CPUs are multicore (e.g. 4 cores)

• Analogy: you have 4 assistants, in theory you could


get the work done 4 times faster!

• In practice:
– it’s hard because you now have to figure out how
to distribute a piece of work amongst 4 people.
– It’s easier to just pass the whole piece of work to
one person.
Why Java 8? (2)

• Vast majority of Java programs use only


one of these cores and leave the others
idle

• Why? writing efficient parallel code is hard


– summing an array with a for loop is easy
– how to sum an array on 4 cores?
Java 8
• Introduces a concise way to pass behaviour
– lambda expressions, method references

• Introduces an API to process data in


parallel
– Streams API
– several operations such as filter, map, reduce can
be parameterised with lambdas

• Also: default methods (more later)


– more flexible inheritance
Behaviour parameterisation
Behaviour parameterisation

• Goal: abstract over behaviour


– Do <something> for every element in a list
– Do <something> else when the list is finished
– Do <yet something else> if an error occurs

• Why should you care?


– Adapt to changing requirements
– Java 8 Streams API heavily relies on it
1st attempt: filtering green apples
List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if("green".equals(apple.getColor() ) {
result.add(apple);
}
}
return result;
}
2nd attempt: abstracting color
public static List<Apple>
filterApplesByColour(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if (apple.getColor().equals(color))) {
result.add(apple); }
}
return result;
}

List<Apple> greenApples = filterApplesByColor(inventory, "green");


List<Apple> redApples = filterApplesByColor(inventory, "red");
2nd attempt: abstracting weight
public static List<Apple>
filterApplesByWeight(List<Apple> inventory, int weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if (apple.getWeight() > weight) {
result.add(apple); }
}
return result;
}

List<Apple> heavyApples = filterApplesByWeight(inventory, 150);


List<Apple> megaHeavyApples = filterApplesByWeight(inventory, 250);
4th (a) attempt: modeling selection criteria
public interface ApplePredicate{
public boolean test (Apple apple);
}

public class AppleWeightPredicate implements ApplePredicate{


public boolean test(Apple apple){
return apple.getWeight() > 150;
}
}
public class AppleColorPredicate implements ApplePredicate{
public boolean test(Apple apple){
return "green".equals(apple.getColor());
}
}
4th (a) attempt: modeling selection criteria
4th (b) attempt: filtering by an abstract
criteria

public static List<Apple>


filter(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if(p.test(apple)) {
result.add(apple);
}
}
return result;
}
Let’s pause for a little bit

• Our code is much more flexible!


– can create any kinds of selection criteria on an Apple
– re-use of code for filter

• Declaring many selection criterias using classes is


verbose
– we have the right abstraction but not good concision
– we need a better way to create and pass behaviours
5th attempt: anonymous classes
List<Apple> result = filter(inventory, new ApplePredicate() {
public boolean test(Apple apple){
return "red".equals.(apple.getColor());
}
});

List<Apple> result = filter(inventory, new ApplePredicate() {


public boolean test(Apple apple){
return apple.getWeight() > 150;
}
});
6th attempt: Java 8 lambdas
List<Apple> result =
filter(inventory,
(Apple apple) -> "red".equals(apple.getColor()));

List<Apple> result =
filter(inventory, (Apple apple) -> apple.getWeight() > 150);

Great because its closer to the problem statement!


7th attempt: abstracting over the
list type
public static <T> List<T> filter (List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for(T e: list){
if(p.test(e)) {
result.add(e);
}
}
return result;
}
8th attempt: Java 8 lambdas again
List<String> result =
filter(strings, (String s) -> s.endsWith("JDK"));

List<Integer> result =
filter(numbers, (Integer i) -> i % 2 == 0);

List<Apple> result =
filter(inventory, (Apple a) -> apple.getWeight() >
150);
Moral of the story

• Behaviour parameterisation lets you write


more flexible code
– Adapt for changes
– Avoids code duplication

• To encourage this style of programming we


need a concise way to create and pass
behaviours
– Java 8 brings lambda expressions to help!
Real world example: Sorting

inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
Real world example: Runnable

Thread t = new Thread(new Runnable() {


public void run() {
System.out.println("Hello world");
}
});
Real world example: GUI events

Button button = new Button("Send");


button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
label.setText("Sent!!");
}
});
PrettyPrint Live Coding Example
What is a Lambda Expression?
Lambda Expressions: what is it?

• A lambda expression is:


– a kind of anonymous function
– that can be passed around:
– It doesn’t have a name, but it has a list of
parameters, a body, a return type, and also
possibly a list of exceptions that can be
thrown.
In a nutshell
Lambda expressions: what is it?

• A lambda expression is:


– a kind of anonymous function

• Anonymous: doesn’t have a name like a


method: less to write and think about!

• Function: not associated to a class like a


method is
Lambda expressions: what is it?

• A lambda expression is:


– a kind of anonymous function
– that can be passed around:

• Passed around: A lambda expression


can be passed as argument to a method
or stored in a variable
Lambda expressions: what is it?

• A lambda expression is:


– a kind of anonymous function
– that can be passed around:
– it doesn’t have a name, but it has a list of
parameters, a body, a return type, and also
possibly a list of exceptions that can be
thrown.

• Concise: you don’t need to write a lot of


boilerplate.
Goal

• Goal: let you pass a piece of


behaviour/code in a concise way

• You can cope with changing requirements


by using a behaviour, represented by a
lambda, as a parameter to a method

• You no longer need to choose between


abstraction and concision!
Before/After

Before:
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});

After:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().
compareTo(a2.getWeight()));
Syntax

(parameters) -> expression

or

(parameters) -> { statements; }


Examples
Use case Example of lambda
A boolean expression (List<String> list) -> list.isEmpty()

Creating objects () -> new Apple(10)


Consuming from an object (Apple a) -> {
System.out.println(a.getWeight());
System.out.println(a.getColor());
}
Select/extract from an object (String s) -> s.length()
Combine two values (int a, int b) -> a * b
Compare two objects (Apple a1, Apple a2) -> a1.getWeight().
compareTo(a2.getWeight())
Quiz: which are not valid?

1. () -> {}
2. () -> "Raoul"
3. () -> {return "Richard";}
4. (Integer i) -> return "James" + i;
5. (String s) -> {"Raoul";}
Functional interfaces: where to use
lambdas?
Where and how to use lambdas?

• You could pass a lambda expression to


filter:
List<Apple> result =
filter(inventory, (Apple apple) -> "red".equals(apple.getColor()));

• Why? Because filter expects a


functional interface as second argument
Functional interface (1)

• The type of a lambda expression is essentially


a functional interface

• A functional interface is an interface that


declares exactly one (abstract) method.
Functional interface (1)

public interface ApplePredicate {


public boolean test (Apple a);
}

• declares only one method called test

public static List<Apple>


filter(List<Apple> inventory, ApplePredicate p) {
...
}
Functional interface (2)
// java.util.Comparator
public interface Comparator<T> {
public int compare(T o1, T o2);
}
// java.lang.Runnable
public interface Runnable {
public void run();
}
// java.awt.event.ActionListener
public interface ActionListener extends
EventListener {
public void actionPerformed(ActionEvent e);
}
Quiz: Functional interface

public interface Adder {


public int add(int a, int b);
}

public interface SmartAdder extends Adder {


public int add(double a, double b);
}

public interface Nothing {


}
Functional interface & Lambdas

• Lambda expressions let you:

• provide the implementation of the abstract


method of a functional interface directly inline.

• treat the whole expression as an instance of a


concrete implementation of a functional interface.
(just like anonymous classes)
Runnable & Lambdas
Runnable r1 = () -> System.out.println("Hello World 1");

Runnable r2 = new Runnable() {


public void run(){
System.out.println("Hello World 2");
}
};
public void process(Runnable r) {
r.run();
}
process(r1);
process(r2);

process(() -> System.out.println("Hello World 3"));


Functional interface & Lambdas

• The signature of the abstract method of


the functional interface essentially
describes the signature of the lambda
expression.
• We call this abstract method a function
descriptor
e.g:
Runnable: () -> void
public void run()
Comparator<T>: (T, T) -> int
public int compare(T o1, T o2)
Comparator & Lambdas

Before:
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});

After:
inventory.sort(
(Apple a1, Apple a2) ->
a1.getWeight().compareTo(a2.getWeight()));
Quiz: Where to use lambdas (1)

execute(() -> {});

public void execute(Runnable r){


r.run();
}
Quiz: Where to use lambdas (2)

ApplePredicate p = (Apple a) -> a.getWeight();

public interface ApplePredicate{


public boolean test(Apple a);
}
Quiz: Where to use lambdas (3)

public Callable<String> fetch() {


return () -> "Tricky example ;-)";
}

public interface Callable<V> {


public V call();
}
Functional interfaces starter kit

Functional interface Lambda signature


Predicate<T> T -> boolean
Consumer<T> T -> void
Function<T, R> T -> R
Supplier<T> () -> T
UnaryOperator<T> T -> T
BinaryOperator<T> (T, T) -> T
BiFunction<T, U, R> (T, U) -> R

• Have a look in java.util.function.*


Primitive specialisations

• IntPredicate, LongPredicate, DoublePredicate

• IntConsumer, LongConsumer, DoubleConsumer

• IntFunction, IntToDoubleFunction, IntToLongFunction,


LongFunction, LongToDoubleFunction,
LongToIntFunction

• IntUnaryOperator, LongUnaryOperator,
DoubleUnaryOperator

• …
Functional interfaces starter kit
examples
Predicate<List<String>> p = (List<String> list) -> list.isEmpty();

Supplier<Apple> s = () -> new Apple(10);

Function<String, Integer> f1 = (String s) -> s.length();


ToIntFunction<String> f2 = (String s) -> s.length();

Consumer<Apple> c =
(Apple a) -> System.out.println(a.getWeight());

BinaryOperator<Integer> op = (Integer a, Integer b) -> a * b;


@FunctionalInterface

@FunctionalInterface
public interface Predicate<T>{
public boolean test(T);
}
Exercise

Refactor the code to use lambda expressions and


standard functional interfaces from java.util.function.*

com.java_8_training.problems.lambdas.LambdaRefactor
Method references
Method references

• Method references let you reuse


existing method definitions and pass
them just like lambdas.
• « First-class » functions

Before: (Apple a) -> a.getWeight()


After: Apple::getWeight

Before: (String str, int i) -> str.substring(i)


After: String::substring
Method references to static methods (1)

Function<String, Integer> converter = Integer::parseInt;


Integer number = converter.apply("10");

Supplier<List<Integer>> emptyList = Collections::emptyList;


List<Integer> numbers = emptyList.get();
Method references to an instance method of
an arbitrary type (2)

Lambda expression Method reference


(Apple a) -> a.getWeight() Apple::getWeight

(String s) -> s.length() String::length

(String str, int i) -> str.substring(i) String::substring

● you are referring to a method to an object that will be


supplied as the first parameter of the lambda.
Method references to an already existing
object (3)

Lambda expression Method reference


() -> expensiveTransaction.getValue() expensiveTransaction::getValue

(Integer index) -> listOfNumbers.get(index) listOfNumbers::get

() -> this.process() this::process

● you are referring to a situation when we are calling a method


in a lambda to an “external” object that already exists.
Recipes
Method references: example
List<String> str = Arrays.asList("a","b","A","B");

Before

str.sort((String s1, String s2)


-> s1.compareToIgnoreCase(s2));

After

str.sort(String::compareToIgnoreCase);
Quiz: method references

What are equivalent method references for the following


lambda expressions?

1. Function<String, Integer> stringToInteger =


(String s) -> Integer.parseInt(s);

2. BiPredicate<List<String>, String> contains =


(list, element) -> list.contains(element);

com.java_8_training.problems.lambdas.MethodRefsQuizTest
Quiz: method references (answers)

Answers:
1. Function<String, Integer> stringToInteger =
(String s) -> Integer.parseInt(s);

Function<String, Integer> stringToInteger


= Integer::parseInt;

2. BiPredicate<List<String>, String> contains


= (List<String> list, String element)
-> list.contains(element);

BiPredicate<List<String>, String> contains


= List::contains;
Better code with Java 8: a practical example

public class AppleComparator implements Comparator<Apple> {


public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}

inventory.sort(new AppleComparator());
Anonymous class

inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
});
Lambdas
inventory.sort(
(Apple a1, Apple a2) ->
a1.getWeight().compareTo(a2.getWeight())
);
Lambdas
inventory.sort(
(Apple a1, Apple a2) ->
a1.getWeight().compareTo(a2.getWeight())
);

With type inference:


inventory.sort(
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight())
);
A bit of help from library

Comparator<Apple> byWeight =
Comparator.comparing((Apple a) -> a.getWeight());

inventory.sort(byWeight);
Method references

Comparator<Apple> byWeight =
Comparator.comparing(Apple::getWeight);

inventory.sort(byWeight);
Tidy up
import static java.util.Comparator.comparing;

inventory.sort(comparing(Apple::getWeight));

Reads like problem statement!


Advanced details
Type checking

• The type of a lambda is deduced from


the context in which the lambda is used
– assignment
– method invocation
– cast expression
– return expression
– …
Type checking

• The type of a lambda is deduced from


the context in which the lambda is used
– assignment
– method invocation
– cast expression
– return expression
– …
• The type expected for the lambda
expression inside a context is called the
target type.
Type checking
Quiz: Type checking

Does the following code compile?

Object o = () -> {System.out.println(“Tricky example”); };


Quiz: Type checking

Does the following code compile?

Object o = () -> {System.out.println(“Tricky example”); };

No as target type is Object, but it isn’t a functional interface


Answer:

Object o =
(Runnable) () -> {System.out.println(“Tricky example”); };
Type inference (1)

• Remember target typing?

• The compiler infers which functional interface


to associate with a lambda expression from
the context

• Because of this, the compiler knows what are


the types of a lambda expression’s formal
parameters!
Type inference (2)
Comparator<Apple> c = (Apple a1, Apple a2) ->
a1.getWeight().compareTo(a2.getWeight());

Comparator<Apple> c = (a1, a2) ->


a1.getName().compareTo(a2.getName());

Function<String, Integer> m = (String x) -> Integer.parseInt(x);

Function<String, Integer> m = (x) -> Integer.parseInt(x);


Type inference (3)

• The are no rules to when to use type inference and


when not
• Sometimes having types helps understanding
• Sometimes having no types is more concise
Scoping rules

• Names in the body of a lambda are interpreted just as


they are in the enclosing context

• this refers to enclosing class (different for anonymous


inner classes!)

• As a result lambda arguments can’t clash with


variables in enclosing environment
int x = 5;
Function<String, Integer> m = (String x) -> Integer.parseInt(x);
// error: variable x is already defined
Using local variables (1)

• Lambda expressions can use variables from their


context
• Capture of local variables is not allowed unless they
are explicitly final or effectively final (statically
assigned only once)
• Restriction doesn’t apply to instance or static
variables

int sum = 0;
list.forEach((Integer i) -> { sum += i; });
// illegal; local variable 'sum' is not effectively final
Using local variables (2) : why the
restriction?
• Technical reason:
• each thread has its own stack, locals are stored on
stack
• what if thread A has a local and then thread B tries
to access it
• Now A de-allocates it - What’s with B?

Design decision:
• same semantics as inner classes
• mutability of locals is undesirable because it
encourages sequential patterns!
Capturing variables : hack restriction?

• Can you think of a way to go around the restriction?


(not recommended - not thread safe!!)

final int[] sum = new int[1];


sum[0] = 0;
list.forEach((Integer i) -> { sum[0] += i; });
// compiles
Exercise

• List all hidden files in the main directory


using File.listFiles
• An anonymous class
• A method reference

List all files ending with “.xml” using File.


listFiles
• A lambda

com.java_8_training.problems.lambdas.WrapUpTest
Summary
Java 8

• Introduces a concise way to pass behaviour


– lambda expressions, method references
• Introduces an API to process data in parallel
– Streams API
– several operations such as filter, map, reduce can
be parameterised with lambdas
• Also: default methods (more later)
– more flexible inheritance
In a nutshell
Functional interfaces starter kit

Functional interface Lambda signature


Predicate<T> T -> boolean
Consumer<T> T -> void
Function<T, R> T -> R
Supplier<T> () -> T
UnaryOperator<T> T -> T
BinaryOperator<T> (T, T) -> T
BiFunction<T, U, R> (T, U) -> R

• Have a look in java.util.function.*


The End
Sequential sum

int sum = 0;
for(Integer i : listOfIntegers){
sum += i;
}
Parallel Sum

• java.lang.Runnable?
• synchronized?
• Monitors?
• Message Passing?
• Work-stealing?
• Fork/Join (Java7)?
Parallel sum: Fork/Join
Exercise (1)

You are to write a prettyPrintApple method that takes a List


of Apples and that can be parameterized with multiple ways
to generate a String output from an Apple (a bit like
multiple customized toString methods).

For example, you could tell your prettyPrintApple method


to print only all the weight of the apples In addition, you
could to tell your prettyPrintApple method to print each
apple individually and mention whether they are “heavy” or
“light”.

com.java_8_training.problems.lambdas.
BehaviourParameterisation
Exercise (2)
Towards prettyPrint...

1. Implement the method prettyPrintOnlyWeightApple


(List<Apple> inventory): it should just print the weight of
each apple iteratively
2. Can you abstract the formatting algorithm? (i.e. how to
generate a string from an apple)
3. Gradually work towards a method prettyPrintApple
which takes a list of apples and an formatter operation
to execute on each apple
4. Can you make it generic? (not only apples, but any
type?)
Three concepts

• Java 8 introduces features that make


parallel data processing easy

• Driven from three concepts:


– stream processing
– behaviour parameterisation ("passing
code")
– no shared mutable data
Stream processing

cat file1 file2 | tr "A-Z" "a-z"


| sort | tail -3
Stream processing

List<String> lowCaloricDishesName =
dishes.parallelStream()
.filter(d -> d.getCalories() < 400)
stream
.sorted(comparing(Dish::getCalories))
processing
.map(Dish::getName)
.collect(toList());
Behaviour parameterisation
Behaviour parameterisation

List<String> lowCaloricDishesName = lambda expression


dishes.parallelStream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
method reference
No shared mutable data

List<String> lowCaloricDishesName =
dishes.parallelStream()
.filter(d -> d.getCalories() < 400) No shared
.sorted(comparing(Dish::getCalories)) variable
.map(Dish::getName) or object
.collect(toList());
3rd attempt: filtering with everything
public static List<Apple>
filter (List<Apple> inventory, String color, int weight,
boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if ( (flag && apple.getColor().equals(color))
|| (!flag && apple.getWeight() > weight) ){
result.add(apple); }
}
return result;
}

List<Apple> greenApples = filter(inventory, "green", 0, true);


List<Apple> heavyApples = filter(inventory, "", 150, false);

Potrebbero piacerti anche