1 of 33

Lambda Expressions

CS 240: Advanced Programming Concepts

2 of 33

Lambda Expression Overview

  • Lambda expression = a block of code with the specification of any parameters that must be passed to it, that can be stored in variables, passed as a parameter, and executed later
  • Similar to Runnables (blocks of code that can be executed later in a separate thread)
  • Similar to event handlers (blocks of code that can be executed later in response to an event)
  • Similar to Comparators (blocks of code that can be executed to sort a collection)
  • Before Java 8, these blocks of code had to be created in a separate class

3 of 33

Lambda Expression Benefits

  • With lambda expressions, it’s easy to store code in variables, pass code as parameters, and write code to be used in event handlers, threads, sort algorithms, etc.
    • It was always possible, but until Java 8, it required the creation of a separate class to hold the code
  • Lambdas add the functional programming paradigm to Java

4 of 33

Lambda Expression Example

  • Consider a simple comparator that orders Strings from shortest to longest:

class StringLengthComparator implements Comparator<String> {

public int compare(String first, String second) {

return Integer.compare(first.length(), second.length());}

}

  • We can pass this comparator to the Arrays.sort method to sort Strings by length:

Arrays.sort(stringArray, new StringLengthComparator());

  • Same example with a lambda expression (no need to create Comparator class):

Arrays.sort(stringArray, (first, second) -> Integer.compare(first.length(), second.length()));

5 of 33

6 of 33

How Java Lambdas Work

7 of 33

How Java Lambdas Work

  1. JVM infers at runtime a data type for the lambda expression (more on that later), a return type (or void) for the expression, and parameter types (unless explicitly specified), from the method being called (in the previous example, Arrays.sort(...))
  2. JVM constructs an in-memory instance of a class expected by the called method with a single method whose body is constructed from the lambda expression (the part after ->)
  3. The part before -> names the parameters in the method to be created and acts as a declaration, allowing the rest of the expression to refer to the parameters
  4. The constructed instance is passed as a parameter in the method call
    1. The method receiving the lambda decides when or if to call it and what parameters to pass

8 of 33

Lambda Expression Example Explained

Arrays.sort(stringArray, (first, second) -> Integer.compare(first.length(),

second.length()));

  1. JVM infers that the method being called is: Arrays.sort(T[] a, Comparator<? super T> c)with T being String (assuming the data type of the stringArray parameter is String [])
  2. JVM constructs an instance of Comparator<String> that looks like this:

new Comparator<String>() {

public int compare(String first, String second) {

return Integer.compare(first.length(), second.length());

}

}

3. JVM invokes the sort method with the Comparator instance passed as the second parameter

9 of 33

Functional Interfaces

  • What data types can be used for lambda expressions (for variables holding them or parameters receiving them)?

Functional Interfaces

  • What are functional interfaces?

Any interface with exactly one abstract method (can contain any number of static, default, and redeclared Object methods)

  • Examples:
    • Comparable
    • Runnable
    • Some event handlers
    • Various other interfaces
      • Including 43 interfaces designed for this purpose in the java.util.function package
      • Including interfaces you create that meet the above definition

10 of 33

11 of 33

Lambda Syntax

12 of 33

Lambda Syntax

(Parameter List) -> Body

Parameter List

Comma separated list of formal parameters

Data types are optional and can be inferred unless ambiguous in callee

Parentheses are optional for a single parameter

Empty parentheses are required for an empty parameter list

->

Arrow token required between parameter list and body

Body

A single expression or a statement block

Return is inferred for a single expression

Statement blocks are enclosed in curly braces

Return must be explicitly specified (unless void) for statement blocks

13 of 33

14 of 33

Function/Lambda Variables

15 of 33

Creating Function (lambda) Variables

public class LambdaVariable {

Predicate<String> strLenPredicate = s -> s.length() > 10;

}

  • Creates a predicate function (a function with a test method that returns a boolean) that in this case returns true if the String’s length is > 10.
  • The data type of the lambda expression is inferred by context (by the type of the variable to which it’s being assigned–in this case Predicate<String>)
  • Can be passed as a parameter to any method that takes a Predicate<String> parameter
  • Can be invoked by calling it’s test(String) method
  • Predicate is one of the 43 interfaces in the java.util.function package (you know what method to call by finding the abstract method in the API docs)

16 of 33

17 of 33

Creating APIs with Lambdas

18 of 33

Creating APIs with Lambdas

Lambdas allow the creation of powerful, elegant APIs in your own code:

public class StringSelectorExample {

public static List<String> select(Collection<String> strings, Predicate<String> selector) {

List<String> selectedStrings = new ArrayList<>();

for (String aString : strings) {

if (selector.test(aString)) {

selectedStrings.add(aString);

}

}

return selectedStrings;

}

}

19 of 33

Creating APIs with Lambdas (cont.)

Consider calling select with the lines of Robert Frost’s poem “The Road Not Taken”, and different selectors:

select(strings, x -> true)

Two roads diverged in a yellow wood,

And sorry I could not travel both

And be one traveler, long I stood

And looked down one as far as I could

To where it bent in the undergrowth;

Then took the other, as just as fair,

And having perhaps the better claim,

Because it was grassy and wanted wear;

...

public class StringSelectorExample {

public static List<String> select(

Collection<String> strings,

Predicate<String> selector) {

List<String> selectedStrings =

new ArrayList<>();

for (String aString : strings) {

if (selector.test(aString)) {

selectedStrings.add(aString);

}

}

return selectedStrings;

}

}

20 of 33

Creating APIs with Lambdas (cont.)

select(strings, x -> x.startsWith("I "))

I doubted if I should ever come back.

I shall be telling this with a sigh

I took the one less traveled by,

select(strings, x -> x.startsWith("And "))

And sorry I could not travel both

And be one traveler, long I stood

And looked down one as far as I could

And having perhaps the better claim,

And both that morning equally lay

And that has made all the difference.

select(strings, x -> x.contains("road"))

Two roads diverged in a yellow wood,

Two roads diverged in a wood, and I-

public class StringSelectorExample {

public static List<String> select(

Collection<String> strings,

Predicate<String> selector) {

List<String> selectedStrings =

new ArrayList<>();

for (String aString : strings) {

if (selector.test(aString)) {

selectedStrings.add(aString);

}

}

return selectedStrings;

}

}

21 of 33

22 of 33

Using Generic Interfaces Example Revisited

(with lambdas)

23 of 33

Using a Generic Interface (revisted)

public interface Function<T, R> {

R apply(T param);

}

public class Capitalizer implements

Function<String, String> {

@Override

public String apply(String param) {

return param == null ? null :

param.toUpperCase();

}

}

public class StringManipulator {

public String manipulateString(String str,

Function<String, String> manipulationFunction) {

return manipulationFunction.apply(str);

}

}

public static void main(String[] args) {

Function<String, String> cap = new Capitalizer();

var sm = new StringManipulator();

String s = sm.manipulateString(args[0], cap);

}

24 of 33

Using a Generic Interface (revisted)

public interface Function<T, R> {

R apply(T param);

}

public class Capitalizer implements

Function<String, String> {

@Override

public String apply(String param) {

return param == null ? null :

param.toUpperCase();

}

}

import java.util.functions.Function;

public class StringManipulator {

public String manipulateString(String str,

Function<String, String> manipulationFunction) {

return manipulationFunction.apply(str);

}

}

public static void main(String[] args) {

var sm = new StringManipulator();

String s = sm.manipulateString(args[0],

x -> x == null ? null : x.toUpperCase());

}

This is one of the 43 interfaces in the java.util.function package

This can be replaced with a lambda expression

25 of 33

Using a Generic Interface (revisted)

manipulateString("my string",

str -> str == null ? null : str.toUpperCase())

MY STRING

manipulateString("my string with spaces that will be removed",

str -> str == null ? null :

str.replaceAll(" ", ""))

mystringwithspacesthatwillberemoved

public class StringManipulator {

public String manipulateString(

String str,

Function<String, String> manipulationFunction) {

return manipulationFunction.apply(str);

}

}

26 of 33

27 of 33

Use of Lambdas in Existing Java Classes

28 of 33

Use of Lambdas in Existing Java Classes

Use of Lambda functions has been integrated throughout the Java API, especially in the collections API.

Example:

List<Integer> intList = new ArrayList<>();

intList.addAll(Arrays.asList(23, 5, 10, 71, 100, 1203, 4, 7, 748));

intList.removeIf(x -> x >= 100);

for(Integer value : intList) {

System.out.print(value + " ");

}

Prints: 23 5 10 71 4 7

29 of 33

30 of 33

Method References

31 of 33

Method References

Simplified syntax for lambda expressions that simply call an existing method

Consider a lambda expression that uses the Java forEach(Consumer<? super T>) method (defined in Iterable) to print the contents of a list:

List<Integer> intList = Arrays.asList(23, 5, 10, 71);

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

The lambda expression simply calls an existing method, passing it’s parameter to the method, so it can be replaced with a method reference:

List<Integer> intList = Arrays.asList(23, 5, 10, 71);

intList.forEach(System.out::println);

32 of 33

Method References (cont.)

  • The double colon indicates a method reference, instead of a method call.
  • A parameter list is not needed or allowed for a method reference and there is no -> operator.
  • Method references can be used for static method, instance method, and constructor invocations:
    • Static method: ClassName::methodName
    • Instance method: objectReference::methodName (equivalent to x -> objectReference.methodName(x))
    • Instance Method (with instance defined as the first parameter in the expression): ClassName::methodName (equivalent to (x, y) -> x.methodName(y))
    • Constructor invocation: ClassName::new

33 of 33