11. Lambda Expressions Lesson¶
Lambda Expressions¶
Lambda Expressions
Lesson Objectives
Motivate and introduce lambda Expressions.
Part 1: Another Generic max method?
Towards Generic Interfaces
Example 1: Drawable
The Drawable interface
Remember: Drawable was an interface that captured
common functionality. All implementing classes have a
draw method and can be drawn.
This interface is NOT generic.
Activity: Using the Drawable interface
public static void test(Drawable d) {
d.______;
} // test
With your group, write which method(s) can be called
using variable d. Then, write down where the code
for the method(s) comes from.
Solution
Since any object that d refers to is an object
of some class and all classes have java.lang.Object
at the top of their hierarchy, we can call draw and
all instance methods of the Object class using d.
However, since an interface is used as the
datatype for d, it is probably the
programmer's intention to call the method
required by that interface inside the method.
Example 2: StringComparator
The StringComparator interface
Similarly, StringComparator captures the common
functionality of comparing strings. All implementing classes
contain a method called compare that allows two strings
to be compared.
This interface is NOT generic.
public static void test(StringComparator sc) {
sc.______;
} // test
With your group, write which method(s) can be called
using variable sc. Then, write down where the code
for the method(s) comes from.
Implement the StringComparator interface
1public class LengthComparator implements StringComparator {
2
3 /**
4 * Compares its two arguments by length. Returns a
5 * negative integer if the first argument is shorter
6 * than the second, zero if they are equal length, or
7 * a positive integer if the first argument is longer
8 * than the second.
9 *
10 * @param o1 the first argument to compare.
11 * @param o2 the second argument to compare.
12 * @return the result of the length comparison.
13 */
14 @Override
15 public int compare(String o1, String o2) {
16 //
17 // your code here
18 //
19 } // compare
20
21} // LengthComparator
Write the code for the compare method in
LengthComparator on your exit ticket (and in your own
notes). Be sure to include the method signature. Do not
include a Javadoc comment, unless you need it for your
notes.
Sample Solution
public class LengthComparator implements StringComparator {
@Override
public int compare(String o1, String o2) {
int length1 = o1.length();
int length2 = o2.length();
int result = length1 - length2;
// negative: when length1 is shorter
// equal: when length1 is equal to length2
// positive: when length2 is shorter
return result;
} // compare
} // LengthComparator
Example: Using our StringComparator implementation
With your groups, fill in the blanks below:
1/**
2 * Returns the "max" string in the array. In this we
3 * define "max" as longest.
4 *
5 * @param items the array of strings to compare.
6 * @param sc a reference to an object containing
7 * the compare method.
8 * @return the "max" string.
9 */
10public static String max(String[] items, LengthComparator sc) {
11 String max = ________;
12 for (int i = 1; i < items.length; i++) {
13 String item = ________;
14 int result = ________;
15
16 if (result > 0) {
17 max = item;
18 } // if
19 } // for
20 return max;
21} // max
Solution
1/**
2 * Returns the "max" string in the array. In this we
3 * define "max" as longest.
4 *
5 * @param items the array of strings to compare.
6 * @param sc a reference to an object containing
7 * the compare method.
8 * @return the "max" string.
9 */
10public static String max(String[] items, LengthComparator sc) {
11 String max = items[0];
12 for (int i = 1; i < items.length; i++) {
13 String item = items[i];
14 int result = sc.compare(item, max);
15 // negative: when item is considered shorter
16 // equal: when item is considered equal to max
17 // positive: when item is considered greater
18 if (result > 0) {
19 max = item;
20 } // if
21 } // for
22 return max;
23} // max
Now, fill in the blanks below to call the max method on
the provided string array:
1String[] strs = new String[] { "aa", "abc", "abcd", "a" };
2
3String max = ________; // call max here
4System.out.println(max); // should be abcd
Solution
String[] strs = new String[] { "aa", "abc", "abcd", "a" };
LengthComparator lc = new LengthComparator();
String max = Utility.max(strs, lc); // call max here
System.out.println(max); // should be abcd
Improving the max method
Activity: Multiple max methods
public static String max(String[] items, LengthComparator sc) {
String max = items[0];
for (int i = 1; i < items.length; i++) {
String item = items[i];
int result = sc.compare(item, max);
if (result > 0) {
max = item;
} // if
} // for
return max;
} // max
public static String max(String[] items, VowelCountComparator sc) {
String max = items[0];
for (int i = 1; i < items.length; i++) {
String item = items[i];
int result = sc.compare(item, max);
if (result > 0) {
max = item;
} // if
} // for
return max;
} // max
Can we write max once and in a way that allows us
to plug in different StringComparator
implementations, regardless of whether that
implementation is a LengthComparator,
VowelCountComparator, or something else?
With your group, write the method signature for a
version of the max method that works with
LengthComparator, VowelCountComparator, and
other kinds of StringComparator
implementations. You only need to write down the
signature.
Sample Solution: One max method
public static String max(String[] items, StringComparator sc) { ...
Discussion: Using our max method
public static String max(String[] items, StringComparator sc) { ...
How would we call our new max method in the blanks below?
String[] items = ...; // Assume the array is properly instantiated and holds multiple strings
String longest = _____________; // Call max here
String mostVowels = ____________; // Call max here
Sample Solution
String[] items = ...;
StringComparator byLength = new LengthComparator();
String longest = Driver.max(items, byLength);
StringComparator byVowelCount = new VowelCountComparator();
String mostVowels = Driver.max(items, byVowelCount);
It's "plug and play" since we used an interface!
Discussion: One more update to max (1)
What is a limitation of the current max method?
How could we improve it?
public static String max(String[] items, StringComparator comp) {
Hint
Can we write one method that works for all reference types?
public static String max(String[] items, StringComparator comp) { ...
public static Song max(Song[] items, SongComparator comp) { ...
public static Person max(Person[] items, PersonComparator comp) { ...
public static Dog max(Dog[] items, DogComparator comp) { ...
Example 3: Comparator<T>
Activity: A better max method
public static String max(String[] items, StringComparator comp) { ...
public static Song max(Song[] items, SongComparator comp) { ...
public static Person max(Person[] items, PersonComparator comp) { ...
public static Dog max(Dog[] items, DogComparator comp) { ...
With your group, write the method signature for a version of the max
method that works for any reference type. You only need
to write down the signature.
Sample Solution: A better max method
public static <T> T max(T[] items, Comparator<T> comp) { ...
This assumes that all the objects referred to by comp are
adjusted so that they implement Comparator<T>, an interface
that you were exposed to when you did the reading for your latest
project.
Instead of separate interfaces, each class can implement Comparator<T>,
since the signature for each of their compare method is the same.
To bring everything together, we make the following adjustments:
implements StringComparatortoimplements Comparator<String>implements SongComparatortoimplements Comparator<Song>implements PersonComparatortoimplements Comparator<Person>implements DogComparatortoimplements Comparator<Dog>
Activity: Write the generic max method
public static <T> T max(T[] items, Comparator<T> comp) {
T maxItem = ________; // Line 1
for (int i = 1; i < items.length; i++) {
T item = ________; // Line 2
int result = _______; // Line 3
if (result > 0) {
maxItem = item;
} // if
} // for
return maxItem;
} // max
Write the code for the max method on your exit ticket
(and in your own notes). Be sure to include the method
signature. Do not include a Javadoc comment, unless you
need it for your notes.
Sample Solution: The generic max method
public static <T> T max(T[] items, Comparator<T> comp) {
T maxItem = items[0];
for (int i = 1; i < items.length; i++) {
T item = items[i];
int result = comp.compare(item, maxItem);
if (result > 0) {
maxItem = item;
} // if
} // for
return maxItem;
} // max
Another version for linked lists:
public static <T> T max(Node<T> head, Comparator<T> comp) {
T maxItem = head.getItem();
while (head != null) {
T item = head.getItem();
int result = comp.compare(item, maxItem);
if (result > 0) {
maxItem = item;
} // if
head = head.getNext();
} // while
return maxItem;
} // max
Part 2: Functional Interfaces
Examples: Implementing the Comparator<T> interface
Here, we show how we can implement Comparator<T> using a regular
class and how we transition from a regular class to an
anonymous class to a lambda expression.
Example: Person by age() (ascending)
public class PersonAgeComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.age() - o2.age();
} // compare
} // PersonAgeComparator
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byAge = new PersonAgeComparator();
Person oldest = Driver.<Person>max(persons, byAge);
...
} // main
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byAge = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age() - o2.age();
} // compare
};
Person oldest = Driver.<Person>max(persons, byAge);
...
} // main
Warning
Although the code above will compile, use of anonymous class syntax is highly discouraged, because it includes almost the same amount of boilerplate as the regular class version.
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byAge = public int compare(Person o1, Person o2) {
return o1.age() - o2.age();
};
Person oldest = Driver.<Person>max(persons, byAge);
...
} // main
Warning
The code above will NOT compile, but writing it
like that makes it clear that we are still overriding
the compare method in whatever object byAge
ends up referring to…
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byAge = (Person o1, Person o2) -> {
return o1.age() - o2.age();
};
Person oldest = Driver.<Person>max(persons, byAge);
...
} // main
Important
A lambda expression can only be assigned to a variable with a functional interface as its type. Why?
Warning
There are shorter ways to write the lambda expression shown above, but the version shown above looks more like a method, and everyone in the room has experience writing methods. Don't try to move too fast. Once you are comfortable writing lambda expresisons like the one above, we will start using the shorter syntax.
Example: Person by name() (ascending)
public class PersonNameComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.name().compareTo(o2.name());
} // compare
} // PersonNameComparator
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byName = new PersonNameComparator();
Person last = Driver.<Person>max(persons, byName);
...
} // main
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byName = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.name().compareTo(o2.name());
} // compare
};
Person last = Driver.<Person>max(persons, byName);
...
} // main
Warning
Although the code above will compile, use of anonymous class syntax is highly discouraged, because it includes almost the same amount of boilerplate as the regular class version.
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byName = public int compare(Person o1, Person o2) {
return o1.name().compareTo(o2.name());
};
Person last = Driver.<Person>max(persons, byName);
...
} // main
Warning
The code above will NOT compile, but writing it
like that makes it clear that we are still overriding
the compare method in whatever object byName
ends up referring to…
public static void main(String[] args) {
Person[] persons = ...;
Comparator<Person> byName = (Person o1, Person o2) -> {
return o1.name().compareTo(o2.name());
};
Person last = Driver.<Person>max(persons, byName);
...
} // main
Important
A lambda expression can only be assigned to a variable with a functional interface as its type. Why?
Warning
There are shorter ways to write the lambda expression shown above, but the version shown above looks more like a method, and everyone in the room has experience writing methods. Don't try to move too fast. Once you are comfortable writing lambda expresisons like the one above, we will start using the shorter syntax.
Example: Movie by various (ascending; lambda)
public static void main(String[] args) {
Movie[] movies = ...;
Comparator<Movie> byYear = (Movie o1, Movie o2) -> {
return o1.year().compareTo(o2.year());
};
Comparator<Movie> byRating = (Movie o1, Movie o2) -> {
return o1.rating() - o2.rating();
};
Comparator<Movie> byRuntime = (Movie o1, Movie o2) -> {
return o1.runtime().compareTo(o2.runtime());
};
Comparator<Movie> byCastSize = (Movie o1, Movie o2) -> {
return o1.cast().size() - o2.cast().size();
};
Comparator<Movie> byTaglineLength = (Movie o1, Movie o2) -> {
return o1.tagline().length() - o2.tagline().length();
};
...
} // main
Discussion: How this relates to P3
The two versions of the max method we have developed over
the past two chapters demonstrate the two ways you are
expected to compare items for the Urgency Queue Project
(P3). Take a look at the UML diagram for P3 and discuss how
using a Comparator and a bounded type parameter relates.
Make sure you focus on the differences between the child
classes and understand how they work.
Activity: Exploring the Predicate<T> interface
To get started: search for "Java 17 Predicate" to pull up the
Java API page for java.util.function.Predicate<T>…
Predicate<String> longerThan10 = (String t) -> {
return t.length() > 10;
};
Answer the following questions about the code above on your exit ticket (and in your notes):
Which abstract method declared in
Predicateis being implemented by the lambda expression above? You may need to check the Java API to get the exact name.What method can we call using
longerThan10as the calling object?Using
longerThan10, write code that prints whether or not "Hello, World" contains more than 10 characters.
Sample Solution: Exploring the Predicate<T> interface
Predicate<String> longerThan10 = (String t) -> {
return t.length() > 10;
};
System.out.println(longerThan10.test("hello")); // ?
System.out.println(longerThan10.test("hello, world")); // ?
Discussion: The <T>printlnMatches method
/**
* Prints the elements of the array that pass the test specified by the given
* predicate. Each element will be printed on its own
* line.
*
* @param <T> the type of the array elements
* @param items the specified array
* @param condition the specified predicate
*/
public static <T> void printlnMatches(T[] items, Predicate<T> condition) {
throw new UnsupportedOperationException("not yet implemented");
} // printlnMatches
Suppose we want to implement the printlnMatches method above.
Is the method
printlnMatchesa generic method? How can you tell?What method can we call using
conditionas the calling object?
Solution
Yes! It has a type parameter,
Tconditionis a reference to an object of a class that implementsPredicate<T>. Therefore, it is guaranteed to have atestmethod.
Activity: Writing the <T>printlnMatches method
/**
* Prints the elements of the array that pass the test specified by the given
* predicate. Each element will be printed on its own
* line.
*
* @param <T> the type of the array elements
* @param items the specified array
* @param condition the specified predicate
*/
public static <T> void printlnMatches(T[] items, Predicate<T> condition) {
throw new UnsupportedOperationException("not yet implemented");
} // printlnMatches
Write the code for the printlnMatches method on your
exit ticket (and in your own notes). Be sure to include the
method signature. Do not include a Javadoc comment, unless
you need it for your notes.
Sample Solution: Writing the <T>printlnMatches method
public static <T> void printlnMatches(T[] items, Predicate<T> condition) {
for (int i = 0; i < items.length; i++) {
T item = items[i];
if (condition.test(item)) {
System.out.println(item);
} // if
} // for
} // printlnMatches
Activity: Calling the <T>printlnMatches method
Finish the code below so that printlnMatches prints all strings containing
the letter a:
String[] myStrings = new String[] {
"CSCI", "1302", "is", "an", "awesome", "course!"
};
Predicate<String> containsA = _____________; // Blank 1
Driver.<String>printlnMatches(_________, _________); // Blanks 2 and 3
Sample Solution: Calling the <T>printlnMatches method
String[] myStrings = new String[] {
"CSCI", "1302", "is", "an", "awesome", "course!"
};
Predicate<String> containsA = (String t) -> {
return t.contains("a");
};
Driver.<String>printlnMatches(myStrings, containsA);
Part 3: Hands-on Activity
Download starter code
Download the starter code for this chapter and places it into
a subdirectory called cs1302-gen-methods:
sh -c "$(curl -fsSL https://cs1302book.com/_bundle/cs1302-gen-methods.sh)"
- downloading cs1302-gen-methods bundle...
- verifying integrity of downloaded files using sha256sum...
- extracting downloaded archive...
- removing intermediate files...
subdirectory cs1302-gen-methods successfully created
Activity: Fill in the blank: <T>print
Finish the code below so that the print method functions
according to the specification:
/**
* Prints all of the provided list. Each element will be printed
* on its own line.
*
* @param <T> the type of the list elements
* @param list the specified list
*/
public static <T> void print(List<T> list) {
for (_____ elem: list) { // Blank 1
System.out.println(_________); // Blank 2
} // for
} // print
Sample Solution: <T>print
1public static <T> void print(List<T> list) {
2 for (T elem: list) { // Blank 1
3 System.out.println(elem); // Blank 2
4 } // for
5} // print
Activity: Fill in the blanks: <T>printlnMatches
Finish the code below so that print functions according
to the specifications:
1/**
2 * Prints the elements of the list that pass the test specified by the given
3 * predicate. Each element will be printed on its own line.
4 *
5 * @param <T> the type of the list elements
6 * @param list the specified list
7 * @param condition the specified predicate
8 */
9 public static <T> void printlnMatches(List<T> list, Predicate<T> condition) {
10 for (_____ elem: list) { // Blank 1
11 if (_______________) { // Blank 2
12 System.out.println(______); // Blank 3
13 } // if
14 } // for
15 } // printlnMatches
Sample Solution: <T>printlnMatches
1/**
2 * Prints the elements of the list that pass the test specified by the given
3 * predicate. Each element will be printed on its own line.
4 *
5 * @param <T> the type of the list elements
6 * @param list the specified list
7 * @param condition the specified predicate
8 */
9 public static <T> void printlnMatches(List<T> list, Predicate<T> condition) {
10 for (T elem: list) {
11 if (condition.test(elem)) {
12 System.out.println(elem);
13 } // if
14 } // for
15 } // printMatches
Calling <T>printlnMatches
Use the method printlnMatches to print the names of
all movies made after 1999.
Note
You may need to review the Movie class given as part of the
Jar file for Project 3.
public static <T> void printlnMatches(List<T> list, Predicate<T> condition) {
for (T elem: list) {
if (condition.test(elem)) {
System.out.println(elem);
} // if
} // for
} // printlnMatches
Movie[] movies = ...;
// setup a predicate (an object containing a test method)
// call printlnMatches
Movie[] movies = ...;
Predicate<Movie> after1999 = ____;
Driver.<Movie>printlnMatches(____, ____);
Movie[] movies = ...;
Predicate<Movie> after1999 = (Movie t) -> {
Year year1999 = Year.of(1999);
return t.year().after(year1999);
};
Driver.<Movie>printlnMatches(____, ____);
Movie[] movies = ...;
Predicate<Movie> after1999 = (Movie t) -> {
Year year1999 = Year.of(1999);
return t.year().after(year1999);
};
Driver.<Movie>printlnMatches(movies, after1999);
Code Update
Update the code in ModelTests to print all movies made after 1999.
Run the code. Looks a bit messy. Let's update the query.
Discussion: Updated Query - Part 1
What if we only want to print the name of all of our movies?
We already have a print method. Can we adjust it to print
only the name out of each movie?
public static <T> void print(List<T> list) {
for (T elem: list) {
System.out.println(elem + "\n");
} // for
} // print
Discussion: Updated Query
What if we want to print just the name of the movies made
after 1999 (or the name of their director, for example)
instead of calling the toString method to print all of
the information for the movies?
Need a new method with another functional interface. We will call
it printlnMappedMatches.
Discussion: The <T>printlnMappedMatches method
To get started: search for "Java 17 Function" to pull up the
Java API page for java.util.function.Function<T, R>…
/**
* Prints the elements of the array that pas the test
* specified by the given predicate using a string
* mapper. Each string-mapped element will be printed on
* its own line.
*
* @param <T> the type of the list elements
* @param list the specified list
* @param condition the specified predicate
* @param mapper the specified string mapper
*/
public static <T> void printlnMappedMatches(
List<T> list, Predicate<T> condition, Function<T, String> mapper
) {
throw new UnsupportedOperationException("not yet implemented");
} // printlnMappedMatches
Suppose we want to implement the printlnMappedMatches method above.
Is the method
printlnMappedMatchesa generic method? How can you tell?What do you think is the purpose of
mapper?What method can we call using
mapperas the calling object?
Solution
Yes! It has a generic type parameter
T.It changes the data type of an element. In this case, it changes the type from
TtoString. That will be perfect for converting aMovieto its name (String).The single, abstract method in
Functionisapply.
Activity: Writing the <T>printlnMappedMatches method
/**
* Prints the elements of the array that pass the test
* specified by the given predicate using a string
* mapper. Each string-mapped element will be printed on
* its own line.
*
* @param <T> the type of the list elements
* @param list the specified list
* @param condition the specified predicate
* @param mapper the specified string mapper
*/
public static <T> void printlnMappedMatches(
List<T> list, Predicate<T> condition, Function<T, String> mapper
) {
throw new UnsupportedOperationException("not yet implemented");
} // printlnMappedMatches
Write the code for the printlnMappedMatches method on your
exit ticket (and in your own notes). Be sure to include the
method signature. Do not include a Javadoc comment, unless
you need it for your notes.
Sample Solution: Writing the <T>printlnMappedMatches method
public static <T> void printlnMappedMatches(
List<T> list, Predicate<T> condition, Function<T, String> mapper
) {
for (int i = 0; i < list.size(); i++) {
T item = list.get(i);
if (condition.test(item)) {
String mappedItem = mapper.apply(item);
System.out.println(mappedItem);
} // if
} // for
} // printlnMappedMatches
Activity: Calling the <T>printlnMappedMatches method
public static <T> void printlnMappedMatches(
List<T> list, Predicate<T> condition, Function<T, String> mapper
) { ...
Use the method printlnMappedMatches to print the names of all movies made after 1999.
List<Movie> movies = ...;
// setup a predicate
// setup a mapper function
// call printlnMappedMatches
List<Movie> movies = ...;
Predicate<Movie> after1999 = ____;
Function<Movie, String> asTitle = ____;
Driver.<Movie>printlnMappedMatches(____, ____, ____);
List<Movie> movies = ...;
Predicate<Movie> after1999 = (Movie t) -> {
Year year1999 = Year.of(1999);
return t.year().after(year1999);
};
Function<Movie, String> asTitle = ____;
Driver.<Movie>printlnMappedMatches(____, ____, ____);
List<Movie> movies = ...;
Predicate<Movie> after1999 = (Movie t) -> {
Year year1999 = Year.of(1999);
return t.year().after(year1999);
};
Function<Movie, String> asTitle = (Movie t) -> {
return t.title();
};
Driver.<Movie>printlnMappedMatches(____, ____, ____);
List<Movie> movies = ...;
Predicate<Movie> after1999 = (Movie t) -> {
Year year1999 = Year.of(1999);
return t.year().after(year1999);
};
Function<Movie, String> asTitle = (Movie t) -> {
return t.title();
};
Driver.<Movie>printlnMappedMatches(movies, after1999, asTitle);