11.4. Another Example¶
Below is another example that illustrates the difference between using
a named class and a lambda expression when implementing
Consumer, a functional interface that comes
with Java (its one abstract method is void accept(T t)
).
The full version of the Consumer
interface defined in Java
contains some additional entities, but a simplified version of it
would look like this:
public interface Consumer<T> {
/**
* Performs this operation on (consumes) the given argument.
* @param t the provided argument.
*/
void accept(T t);
} // Consumer
The concept of a consumer may seem a bit confusing in this context,
but the general idea is that it defines a single, abstract method
called apply
that takes in a generic type argument (of type T
)
and returns nothing. In other words, it takes in the argument and
consumes it (doesn’t return anything).
Now, what the accept
method does with the argument is up to
the implementing class. We get to define that when we build a
Consumer
. The same thing was true with DiscountStrategy
.
When we first defined the interface, the details of the apply
method were not clear. However, they became more clear when
we implemented the interface for various types of discounts.
In the example below, we implement Consumer
using a named class
and a lambda expression. Both take in a String
argument and make
it all caps, so we call our implementing class Shouter
.
Take a few minutes to compare the two implementations side-by-side. In the text below the example, we break down the syntax of the lambda version of the code.
public class Shouter implements Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t.toUpperCase());
} // accept
} // Shouter
public class Driver {
public static void forEach(String[] strings, Consumer<String> consumer) {
for (int i = 0; i < strings.length; i++) {
String str = strings[i];
consumer.accept(str);
} // for
} // forEach
public static void main(String[] args) {
Consumer<String> shout = new Shouter();
Driver.forEach(args, shout);
} // main
} // Driver
Here, we no longer need a separate named class to implement Consumer
!
public class Driver {
public static void forEach(String[] strings, Consumer<String> consumer) {
for (int i = 0; i < strings.length; i++) {
String str = strings[i];
consumer.accept(str);
} // for
} // forEach
public static void main(String[] args) {
Consumer<String> shout = (String t) -> {
System.out.println(t.toUpperCase());
};
Driver.forEach(args, shout);
} // main
} // Driver
Note
In the second example that utilizes the lambda expression syntax, we didn’t create an additional file for a class that implements the interface. However, we did define a class that implements the interface and make an object out of that class. It all happened on one line:
Consumer<String> shout = (String t) -> {
System.out.println(t.toUpperCase());
};
11.4.1. Explaining the Example¶
Let’s break it down:
1Consumer<String> shout = (String t) -> {
2 System.out.println(t.toUpperCase());
3 };
4// -------------------|-|-------------------------------------------------|
5// 1 |3| 2 |
6// the lambda expression
First, a reference variable named
shout
is declared with the typeConsumer<String>
;A lambda expression is used to create an object that has one method by defining what that method should do. In this case, we want the method’s type layout to match the abstract method
accept
inConsumer<String>
, and it does because it takes in a parameter of typeString
(which replacedT
in the original interface) and it returns nothing (println
is a void method).Assign the object’s reference to the variable.
Since we didn’t define the object’s method in some named class, it is considered an object of an unnamed class. That’s okay, so long we don’t need multiple objects of that class.
Test Yourself
Compile and run the example above!
In the next section, we will discuss the recommended process for writing lambda expressions on your own.