Section 14. (Lambda expressions, functional interfaces, method references) Udemy - Java Programming Masterclass
Git
Lambda Expressions
Overview
- introduced in JDK 8
- thought of as implicit code for an anonymous class, using a special kind of interface
- The method to be used is inferred by java
- java requires types which support lambda expressions, to be a functional interface
- considered java’s first step into functional programmming
- Streams make extensive use of lambdas because the form a pipline of work processed in a chain of events
Functional interface
- A functional interface is an interface that has one, and only one abstract method
- referred to as SAM (Single abstract method)
- java provides a library of functional interfaces in java.util.function.package. Just over 40 interfaces and counting.
- 4 types of functional interface types
- Notice for both functions on the left type is R which means it could be a different result type than the parameters
- Notice for both Operators the type input and the type result are the same
Consumer functional interface
- in the
java.util.function
package - One abstract method
void accept(T t);
. it takes in a single argument and doesn’t return anything. - In the following example the ArrayList has a method with the signagure
public void forEach(Consumer<? super E> action) {..}
1
2
3
4
5
6
7
8
9
10
11
List<String> list = new ArrayList<>(List.of(
"alpha", "bravo", "charlie", "delta"
));
// traditional way of printing an arraylist
for(var str : list){
System.out.println(str);
}
// lambda expression print
list.forEach(s -> System.out.println(s));
- example 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(){
var coordinates = Arrays.asList(
new double[alt-text]{47.182, -47.123},
new double[alt-text]{61.222, -77.190},
new double[alt-text]{12.331, -55.399}
);
// using a variable for the lambda expression for the consumer method
BiConsumer<Double, Double> p1 = (lat, lng) -> System.out.printf("[lat:%.3f lon:%.3f]\n", lat, lng);
coordinates.forEach(coordinate -> processPoint(coordinate[0], coordinate[1], p1));
// using a lambda expression for the consumer method to pass to processPoint
coordinates.forEach(coordinate -> processPoint(coordinate[0], coordinate[1], (x,y) -> System.out.printf("[lat:%.3f lon:%.3f]\n", x, y)));
}
public static <T> void processPoint(T t1, T t2, BiConsumer<T, T> consumer){
consumer.accept(t1, t2);
}
Predicate functional interface
- Takes in 1 argument and returns true or false if a specification is met
boolean test(T t);
- This is the abstract method for the predicate functional interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[alt-text] args){
List<String> mylist = new ArrayList<>(List.of(
"Tresten", "Josh", "Dave", "Mike", "Roxy", "Tim"
));
fire_person( (str) -> str.equals("Tresten") || str.equals("Josh"), mylist);
System.out.println(mylist); // Dave, Mike, Roxy, Tim
}
public static void fire_person(Predicate<String> fire_person_predicate, List<String> people){
var iterator = people.iterator();
while(iterator.hasNext()){
var person = iterator.next();
if(fire_person_predicate.test(person)) {
System.out.println("YOUR FIRED!!! " + person);
iterator.remove();
}
}
}
Supplier functional interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String... args){
String[alt-text] mystrings = new String[alt-text]{"one", "two", "three", "four", "five", "six", "seven"};
String[alt-text] randomSelectedValues = randomlySelectedValues(3, mystrings, () -> new Random().nextInt(7));
System.out.println(Arrays.toString(randomSelectedValues));
}
public static String[alt-text] randomlySelectedValues(int count,
String[alt-text] values,
Supplier<Integer> s){
String[alt-text] selectedValues = new String[count];
for(int i = 0; i < count; i++){
selectedValues[i] = values[s.get()];
}
return selectedValues;
}
1
2
3
4
5
6
Consumer<String> consumer = (message) -> {
String[alt-text] parts = message.split(" ");
Arrays.asList(parts).forEach(s -> System.out.println(s));
};
consumer.accept("Hello world, nice to see you");
Convienence Methods
- default methods in the functional interface
- In this example below we show the .andThen() in the Function interface
.andThen() example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String name = "Tresten";
// first function
Function<String, String> uCase = String::toUpperCase;
// apply the first function and print out the results
System.out.println(uCase.apply(name));
// second function
Function<String, String> lastName = (s) -> s.concat(" Pool");
// setup of the first function calling the second one after
Function<String,String> uCaseLastName = uCase.andThen(lastName);
// call the setup method above with the name variable
System.out.println(uCaseLastName.apply(name));
.andThen() chaining example
1
2
3
4
5
6
7
Function<String, String> uCase = String::toUpperCase;
Function<String, String[alt-text]> f0 = uCase
.andThen(s -> s.concat(" Pool")) // return String
.andThen(s -> s.split(" ")); // return String[alt-text]
System.out.println(Arrays.toString(f0.apply("Tresten")));
.andThen() chaining example
1
2
3
4
5
6
7
String[alt-text]names = {"Josh", "Dave", "Bob", "Carrol"};
Consumer<String> s0 = s -> System.out.print(s.charAt(0));
Consumer<String> s1 = System.out::println;
Arrays.asList(names).forEach(s0
.andThen(s -> System.out.print(" - "))
.andThen(s1)
);
Comparator Example
1
default void sort(Comparator<? super E> c){ .. }
1
2
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
1
2
3
4
5
6
// compare by lastname then compare by firstname
list.sort(Comparator.comparing(Person::lastName)
.thenComparing(Person::firstname));
// print out results
list.forEach(System.out::println);
Syntax
Usuage example
- lambda expression parameters are determined by the associated interface’s method
- The method to be used is inferred by java
1
2
3
4
5
6
7
8
9
10
// our list of people
List<Person> people = new ArrayList<>(Arrays.asList(
new Main.Person("Mickey", "Mouse"), // just to show you we can use this syntax because the record is a static nested class
new Person("Donald", "Duck"),
new Person("Goofy", "Mcman"),
new Person("Hubert", "Jensen")
));
// sort with a lambda expression which is a comparable statement
people.sort( (o1, o2) -> o1.firstName.compareTo(o2.firstName) );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Comparator interface that extends Comparator
interface EnhancedComparator<T> extends Comparator<T>{
int secondLevel(T o1, T o2);
}
// concrete class for the above interface
var comparatorMixed = new EnhancedComparator<Person>(){
@Override
public int compare(Person o1, Person o2) {
int result = o1.lastName.compareTo(o2.lastName);
return (result == 0) ? secondLevel(o1, o2) : result; // if the last names are the same, match by the first name too, the second level
}
@Override
public int secondLevel(Person o1, Person o2) {
return o1.firstName.compareTo(o2.firstName);
}
};
// we are unable to use a lambda expression for the intended comparator comparatorMixed because it is not a Functional Interface
// It is not a Functional Interface because it has 2 abstract methods!!
people.sort(comparatorMixed);
System.out.println(people);
Multi line lambda
1
2
3
4
5
6
7
8
9
List<String> list = new ArrayList<>(List.of(
"alpha", "bravo", "charlie", "delta"
));
// multi-line lambda expression to make all the elements in list uppercase
list.forEach(mystring -> {
int index = list.indexOf(mystring);
list.set(index, list.get(index).toUpperCase());
});
Scope
- A lambda expression has access to final variables or effectively final of the enclosing method.
1
2
3
4
5
6
// we are able to use this variable because it is EFFECTIVELY FINAL, meaning it hasn't been changed since its declaration
String prefix = "nato";
list.forEach(mystring -> {
int index = list.indexOf(mystring);
list.set(index, "%s %s".formatted(prefix, list.get(index).toUpperCase()) );
});
Method Reference
- You can just use a method reference in the lambda body
- method references are for easier readibility
- The second example shows just replacing with the method reference
- Both achieve the same result of printing every element in the list
1
backedByArray.forEach(s -> System.out.println(s));
1
backedByArray.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Test{
public static int ID_GENERATOR = 1000;
private int id;
public Test() {
id = ID_GENERATOR++;
System.out.println("Created Test object with id: " + this.id);
}
}
public class Main {
public static void main(String[alt-text] args) {
seedArray(Test::new, 10);
}
public static Test[alt-text] seedArray(Supplier<Test> reference, int count){
Test[alt-text] testArray = new Test[count];
for(int i = 0; i < count; i++){
testArray[i] = reference.get();
}
return testArray;
}
}
Method Reference restrictions
- methods which can be used, are based on the context
- you can reference a static or instance method
This post is licensed under CC BY 4.0 by the author.