11.8. Additional Practice Exercises

Question 1

Review the DiscountStrategy interface below:

public interface DiscountStrategy {
    /**
     * Applies this discount strategy to the specified
     * {@code originalPrice} and returns the adjusted
     * price.
     *
     * @param originalPrice the original price of the item.
     * @return the discounted price.
     */
    double apply(double originalPrice);
} // apply

Now, write two lambda expressions that implement the functional interface to complete the code below.

  1. The first lambda should give the user a 10% discount.

  2. The second should give them $5 off the original price.

 1public class LambdaDiscountDemo {
 2
 3    public static void main(String[] args) {
 4        // Step 1: Instantiate using a lambda expression
 5        DiscountStrategy tenPercent = ___________; // first lambda here
 6        DiscountStrategy fiveDollars = _________; // second lambda here
 7
 8        // Step 2: Interact using the interface
 9        double price = 50.0;
10
11        System.out.printf("Original price: $%.2f%n", price);
12        System.out.printf("10%% off (lambda): $%.2f%n", tenPercent.apply(price));
13        System.out.printf("$5 off (lambda): $%.2f%n", fiveDollars.apply(price));
14    } // main
15} // LambdaDiscountDemo
Solution
 1public class LambdaDiscountDemo {
 2
 3    public static void main(String[] args) {
 4        // Step 1: Instantiate using a lambda expression
 5        DiscountStrategy tenPercent = (double price) -> {
 6            return price * 0.90;
 7        };
 8        DiscountStrategy fiveDollars = (double price) -> {
 9            return Math.max(0.0, price - 5.0);
10        };
11
12        // Step 2: Interact using the interface
13        double price = 50.0;
14
15        System.out.printf("Original price: $%.2f%n", price);
16        System.out.printf("10%% off (lambda): $%.2f%n", tenPercent.apply(price));
17        System.out.printf("$5 off (lambda): $%.2f%n", fiveDollars.apply(price));
18    } // main
19} // LambdaDiscountDemo
Question 2

Below is a class-based implementation of an interface called StringModifier that contains a single method called modify. Rewrite the class as a lambda expression.

 1/**
 2 * A string modifier that converts the input string to uppercase.
 3 */
 4public class UpperCaseConverter implements StringModifier {
 5
 6    /**
 7     * Converts the given input string to uppercase.
 8     *
 9     * @param input the original string
10     * @return the uppercase version of the input string
11     */
12     @Override
13     public String modify(String input) {
14         return input.toUpperCase();
15     } // modify
16} // UpperCaseConverter
Solution

Since this interface has only one abstract method, it qualifies as a functional interface. This allows us to replace the entire class with a concise lambda expression.

Here’s how we can rewrite the class-based implementation as a lambda:

// Lambda version of UpperCaseConverter
StringModifier upperCase = (String input) -> {
    return input.toUpperCase();
};

The lambda takes one parameter, ``input``, and returns
``input.toUpperCase()``.  Theres no need to write an entire class or
method override  the lambda directly defines the behavior of the
single abstract method ``modify``.
Question 3

Convert this class-based implementation to a lambda expression.

 1/**
 2 * A number checker that determines whether a given number is positive.
 3 */
 4public class PositiveChecker implements NumberChecker {
 5
 6    /**
 7     * Checks if the given number is positive.
 8     *
 9     * @param number the integer to check
10     * @return {@code true} if the number is greater than zero; {@code false} otherwise
11     */
12    @Override
13    public boolean check(int number) {
14        return number > 0;
15    } // check
16} // PositiveChecker
Solution

The class PositiveChecker implements an interface called NumberChecker, which probably looks like this:

public interface NumberChecker {
   boolean check(int number);
} // NumberChecker

Since NumberChecker also has one abstract method, we can convert it to a lambda expression.

Here’s the equivalent lambda form:

// Lambda version of PositiveChecker
NumberChecker positive = (int number) -> {
    return number > 0;
};
Question 4

Rewrite the following lambda as a class with a proper method override. Create a class called SquareOperation that implements MathOperation. You can assume the MathOperation interface has a single, abstract method called operate.

1// A lambda expression that squares a given number.
2MathOperation square = (double x) -> {
3    return x * x;
4};
Solution

Now we are going the other way — converting lambda to class.

We can infer that the MathOperation interface is defined as:

1public interface MathOperation {
2    double operate(double x);
3} // MathOperation

Our class would look something like this:

 1/**
 2 * A mathematical operation that returns the square of a given number.
 3 */
 4public class SquareOperation implements MathOperation {
 5
 6    /**
 7     * Squares the given number.
 8     *
 9     * @param x the input number
10     * @return the square of {@code x}
11     */
12    @Override
13    public double operate(double x) {
14        return x * x;
15    } // operate
16
17} // SquareOperation
Question 5

Convert the lambda below into a full class implementation. Create a class called FriendlyGreeting that implements Greeting. You can assume that Greeting has a single, abstract method called greet.

1Greeting greet = (String name) -> {
2    return "Hello, " + name + "!";
3};
Solution

The Greeting interface looks like this:

1public interface Greeting {
2    String greet(String name);
3} // Greeting

Here’s the full class:

 1/**
 2 * A friendly greeting implementation of the Greeting interface.
 3 */
 4public class FriendlyGreeting implements Greeting {
 5
 6    /**
 7     * Returns a friendly greeting message for the given name.
 8     *
 9     * @param name the name of the person to greet
10     * @return a personalized greeting message
11     */
12    @Override
13    public String greet(String name) {
14        return "Hello, " + name + "!";
15    } // greet
16
17 } // FriendlyGreeting
Question 6
  1. Predict the output of the following code:

     1/**
     2 * A functional interface representing an operation that accepts
     3 * a single input argument and returns no result.
     4 *
     5 * @param <T> the type of the input argument
     6 */
     7public interface Consumer<T> {
     8
     9    /**
    10     * Performs this operation on the given argument.
    11     *
    12     * @param t the input argument
    13     */
    14    void accept(T t);
    15} // Consumer
    16
    17/**
    18 * Demonstrates the use of a lambda expression with the Consumer interface.
    19 */
    20public class Driver {
    21
    22    public static void main(String[] args) {
    23
    24        // A lambda expression that greets the given name.
    25        Consumer<String> greet = (String name) -> {
    26            System.out.println("Hello, " + name + "!");
    27        };
    28
    29        greet.accept("John");
    30    } // main
    31} // Driver
    
  2. Explain your reasoning. What happens when the accept method is called on the greet object? Which line of code defines what accept should do?

Solution

Step 1: Identify the Functional Interface

The Consumer<T> interface defines one abstract method, accept(T t). Because it has a single abstract method, it qualifies as a functional interface. This means it can be implemented using a lambda expression instead of a full, named class.

Step 2: Understand What the Lambda Does

1Consumer<String> greet = (String name) -> {
2    System.out.println("Hello, " + name + "!");
3};

The above line of code creates an object of an unnamed class implementing Consumer<String>, defines the behavior of the accept method to print a greeting, and assigns the object to greet.

It can be read as: Given a String parameter name, execute System.out.println("Hello, " + name + "!");. which is equivalent to defining a separate class:

1public class GreetingConsumer implements Consumer<String> {
2    @Override
3    public void accept(String name) {
4        System.out.println("Hello, " + name + "!");
5    } // accept
6} // GreetingConsumer

What happens when the accept method is called on the greet object? When greet.accept("John") is called, the lambda body executes. It prints the message “Hello, John!” to the console. Essentially, the lambda defines the behavior of the accept method, so calling it runs that behavior.

Which line of code defines what accept should do?

The line:

1Consumer<String> greet = (String name) -> {
2    System.out.println("Hello, " + name + "!");
3};

defines what accept should do. The lambda name -> System.out.println("Hello, " + name + "!"); specifies the actions of the accept method.

Question 7

Predict the output of the following code:

 1/**
 2* A functional interface representing an operation that accepts
 3* a single input argument and returns no result.
 4*
 5* @param <T> the type of the input argument
 6*/
 7public interface Consumer<T> {
 8
 9    /**
10    * Performs this operation on the given argument.
11    *
12    * @param t the input argument
13    */
14    void accept(T t);
15
16} // Consumer
17
18/**
19* Demonstrates the use of a lambda expression with the Consumer interface
20* to double integer values.
21*/
22public class Driver {
23
24    /**
25    * The main entry point of the program.
26    *
27    * @param args command-line arguments
28    */
29    public static void main(String[] args) {
30
31        // A lambda expression that doubles the given integer and prints it
32        Consumer<Integer> doubler = (Integer x) -> {
33            System.out.println(x * 2);
34        };
35
36        doubler.accept(7);
37        doubler.accept(10);
38
39    } // main
40} // Driver
Solution

When doubler.accept(7) is called, the lambda executes, printing 7 * 2, which is 14. Immediately after, doubler.accept(10) is called, printing 10 * 2, which is 20. The lambda is reused for each call to accept, taking a different input each time.

Note: The Consumer<Integer> interface defines a single abstract method accept(Integer t). In the code, the lambda expression x -> System.out.println(x * 2) is used to implement this method. This lambda effectively replaces a full class implementation that would normally look like this:

1public class Doubler implements Consumer<Integer> {
2    @Override
3    public void accept(Integer x) {
4        System.out.println(x * 2);
5    } // accept
6} // Doubler
Question 8

What will be printed when this program runs?

 1public interface Consumer<T> {
 2    void accept(T t);
 3} // Consumer
 4
 5public class Driver {
 6
 7    public static void forEach(String[] strings, Consumer<String> consumer) {
 8        for (int i = 0; i < strings.length; i++) {
 9            consumer.accept(strings[i]);
10        } // for
11    } // forEach
12
13    public static void main(String[] args) {
14        String[] names = {"Alex", "Jordan", "Sam"};
15        Consumer<String> shout = s -> System.out.println(s.toUpperCase());
16        Driver.forEach(names, shout);
17    } // main
18} // Driver
Solution

Output:

ALEX
JORDAN
SAM

Step 1: Understanding the Lambda and Method Call

The variable shout is assigned a lambda expression that implements Consumer<String>. This lambda defines what the accept method should do: it takes a string s and prints it in uppercase. Instead of creating a named class with an accept method, the lambda encapsulates the behavior directly in one line.

The forEach method takes an array of strings and a Consumer<String> object. Inside the method, a for loop iterates over each string in the array and calls consumer.accept on it. In this example, shout is passed as the consumer.

Step 2: Program Flow and Execution

When Driver.forEach(names, shout) is called, the for loop iterates over the array {"Alex", "Jordan", "Sam"}. For each element, the lambda body executes, converting the string to uppercase and printing it. Specifically, consumer.accept("Alex") runs first, printing "ALEX". Then consumer.accept("Jordan") executes, printing JORDAN. Finally, consumer.accept("Sam") executes, printing "SAM".

**How many times will the lambda body execute? What happens each

time the forEach loop calls consumer.accept?**

The lambda body executes once for each element in the array. Since there are three elements, the lambda executes three times. Each time the forEach loop calls consumer.accept, the lambda runs with the current array element as its input, performs the toUpperCase operation, and prints the result. The behavior is identical to what would happen if a named class with an overridden accept method were used, but the lambda allows it to happen in a single line without defining a new class.

Question 9

Use a lambda expression to create a Consumer<String> that prints the number of vowels (a, e, i, o, u) in a given string.

Solution
 1Consumer<String> vowelCounter = (String s) -> {
 2    int count = 0;
 3    for (int i = 0; i < s.length(); i++) {
 4        char c = Character.toLowerCase(s.charAt(i));
 5        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
 6            count++;
 7        } // if
 8    } // for
 9    System.out.println("Number of vowels: " + count);
10};

Example usage:

Listing 11.12 In another class
vowelCounter.accept("Hello World"); // Output: Number of vowels: 3
Question 10

Use a lambda expression to create a Consumer<Integer> that prints the square of a number only if it is even; otherwise, it prints “Odd number ignored”. This requires multiple lines in the lambda body.

Solution
 1/**
 2 * A Consumer that prints the square of a number if it is even,
 3 * or prints a message if it is odd.
 4 */
 5Consumer<Integer> evenSquarer = (Integer n) -> {
 6    if (n % 2 == 0) {
 7        System.out.println("Square: " + (n * n));
 8    } else {
 9        System.out.println("Odd number ignored");
10    } // if
11};

Example usage:

Listing 11.13 In another class
evenSquarer.accept(4); // Output: Square: 16
evenSquarer.accept(7); // Output: Odd number ignored
Question 11

Use a lambda expression to create a DiscountStrategy that applies the following rules:

If the original price is greater than $100, reduce it by 20%; Otherwise, reduce it by 10%. Then, print a message “Discount applied” before returning the discounted price. This also requires multiple statements inside the lambda body.

Solution
 1/**
 2 * A DiscountStrategy that applies a tiered discount based on the original price.
 3 */
 4DiscountStrategy tieredDiscount = price -> {
 5    double discountedPrice;
 6    if (price > 100) {
 7        discountedPrice = price * 0.8; // 20% discount
 8    } else {
 9        discountedPrice = price * 0.9; // 10% discount
10    }
11    System.out.println("Discount applied");
12    return discountedPrice;
13};

Example usage:

Listing 11.14 In another class
double price1 = tieredDiscount.apply(120); // Output: Discount applied, returns 96.0
double price2 = tieredDiscount.apply(80);  // Output: Discount applied, returns 72.0