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.

Listing 11.5 in Shouter.java
public class Shouter implements Consumer<String>() {

    @Override
    public void accept(String t) {
        System.out.println(t.toUpperCase());
    } // accept

} // Shouter
Listing 11.6 in Driver.java
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!

Listing 11.7 In Driver.java
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
  1. First, a reference variable named shout is declared with the type Consumer<String>;

  2. 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 in Consumer<String>, and it does because it takes in a parameter of type String (which replaced T in the original interface) and it returns nothing (println is a void method).

  3. 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.