You are on page 1of 26

Java 8 Updates - Lambda Expressions

Date: 27th January 2022


Java 8 Updates

Passing code with behavior parameterization


Functional Interfaces
Lambda Expressions
Method References
Stream API
Interface Default and Static Methods
Optional<T>
Date and Time API
Behavior parameterization

Behavior parameterization is a software development pattern that lets you


handle frequent requirement changes.

It means, taking a block of code and making it available without executing. This
block of code can be called later by other parts of your programs.

You can pass the block of code as an argument to other method that will
execute later.
Passing Code with Behavior Parameterization

In software engineering, user requirements will change.

Example : Imagine an application to help a farmer understand his inventory.


- Find all green apples in the inventory
- Find all apples heavier than 200 g

As a developer, we have to minimize our engineering effort.


ApplePredicate
boolean test(Apple apple)

AppleGreenColorPredicate AppleHeavyWeightPredicate

public interface ApplePredicate {


boolean test(Apple apple);
}

public class AppleGreenColorPredicate implements ApplePredicate {


@Override
public boolean test(Apple apple) {
return apple.getColor().equals(Color.GREEN);
}
}

public class AppleHeavyPredicate implements ApplePredicate {


@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
Strategy Design Pattern

Strategy Design Pattern : which lets you define a family of algorithms,


encapsulate each algorithm, and select an algorithm at run time.

In this case the family of algorithm is ApplePredicate and the different


strategies are AppleHeavyWeightPredicate and AppleGreenColorPredicate.
List<Apple> heavyApple = filterApples(inventory, new AppleHeavyPredicate());
System.out.println(heavyApple);

List<Apple> greenApple = filterApples(inventory, new AppleGreenColorPredicate());


System.out.println(greenApple);

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){


List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if(p.test(apple)){
result.add(apple);
} Behavior Parameterization : the ability to tell a method to take
}
multiple behaviors (strategies)
return result;
}
Task
Write a prettyPrintApple method that takes aList of Apples and that can be parameterized with
multiple ways to generate a String output from an apple

public static void prettyPrintApple(List<Apple> inventory, ???){


for(Apple apple : inventory){
String output = ???.???(apple);
System.out.println(output);
}
}

Sample Output : Sample Output :


A Light GREEN apple An apple of 80g
A Heavy RED apple An apple of 155g
A Light GREEN apple An apple of 120g
public interface AppleFormatter {
String accept(Apple apple);
}

public class AppleSimpleFormatter implements AppleFormatter {


@Override
public String accept(Apple apple) {
return "An apple of " + apple.getWeight() + "g";
}
}

public class AppleFancyFormatter implements AppleFormatter{


@Override
public String accept(Apple apple) {
String characteristic = apple.getWeight() > 150 ? "Heavy" : "Light";
return "A " + characteristic + " " + apple.getColor() + " apple";
}
}

public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter){


for(Apple apple : inventory){
String output = formatter.accept(apple);
System.out.println(output);
}
}
Anonymous Classes

Anonymous classes are like the local classes (class defined in a block).

Anonymous classes do not have a name.

They allow you to declare and instantiate a class at the same time
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return apple.getColor().equals(Color.RED);
}
});

System.out.println(redApples);

Parameterizes the behavior of the method


filterApples with an anonymous class
Lambda Expressions

A lambda expression can be understood as a concise representation of an


anonymous function that can be passed around.

It does not have a name, but it has a list of parameters, a body, and a return
type, and also possibly a list of exceptions that can be thrown.
Arrow

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

Lambda Parameters Lambda body


(String s) -> s.length() Takes one parameter of type String and returns an int.
It has no return statement as return is implied.

(Apple a) -> a.getWeight() > 150 Takes one parameter of type Apple and returns a boolean.

(int x, int y) -> { Takes two parameters of type int and


returns no value (void return).
System.out.println("Result");
Its body contains 2 statements
System.out.println(x+y);
}

() -> 42 Takes no parameter and returns the int 42

(Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight())

Takes 2 parameters of type Apple and returns


an int representing the comparison of their weights
When to use Lambdas?

You can use a lambda expression in the context of a functional interface.


Functional Interface

Functional interface is an interface that specifies exactly one abstract method.

An interface is still a functional interface if it has many default methods as long


as it specifies only one abstract method.

@FunctionalInterface
public interface ApplePredicate {
boolean test(Apple apple);
}
Which of these interfaces are functional interfaces?

public interface Adder {


int add(int a, int b);
}

public interface SmartAdder extends Adder {


int add(double a, double b);
}

public interface Nothing {

}
Java API-Functional Interfaces

Java 8 has several new functional interfaces inside the java.util.function


package :

- Predicate
- Consumer
- Function
- Supplier
- Runnable
Predicate

Predicate<T> interface defines an abstract method named test that accepts an


object of generic type T and returns a boolean.

You can use this interface when you need to represent a boolean expression
that uses an object of type T.

@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Predicate<Integer> lesserThan = new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer<18;
} Without Lambda

};

System.out.println(lesserThan.test(10)); //true

Predicate<Integer> lesserThan = i -> i<18;


With Lambda
System.out.println(lesserThan.test(10)); //true
Consumer

Consumer<T> interface defines an abstract method named accept that takes


an object of generic type T and returns no result (void).

You can use this interface when you need to access an object of type T and
perform some operations on it. For example : forEach

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<Integer> display = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
Without Lambda
}
};

display.accept(10);

Consumer<Integer> display = i -> System.out.println(i);

display.accept(10); With Lambda


Function

Function<T,R> interface defines an abstract method named apply that takes an


object of generic type T as input and returns an object of generic type R.

You can use this interface when you need to define a lambda that maps
information from an input object to an output.

@FunctionalInterface
public interface Function<T,R> {
R apply(T t);
}
Function<Integer, Double> convert =
new Function<Integer, Double>() {
@Override
public Double apply(Integer integer) {
Without Lambda
return integer.doubleValue();
}
};

System.out.println(convert.apply(10)); //10.0

Function<Integer, Double> convert = i -> i.doubleValue();

System.out.println(convert.apply(10)); //10.0 With Lambda


Supplier

Supplier<T> interface defines an abstract method named get that does not take
any argument but produces a value of type T

@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier<Double> randomValue = new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
Without Lambda
}
};

System.out.println(randomValue.get());

Supplier<Double> randomValue = () -> Math.random();


With Lambda
System.out.println(randomValue.get());

You might also like