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.
The first lambda should give the user a 10% discount.
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()``. There’s 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
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
Explain your reasoning. What happens when the
acceptmethod is called on thegreetobject? Which line of code defines whatacceptshould 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:
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:
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:
double price1 = tieredDiscount.apply(120); // Output: Discount applied, returns 96.0
double price2 = tieredDiscount.apply(80); // Output: Discount applied, returns 72.0