CS F213 Objected Oriented Programming Labsheet 12

Spring 2024

Java Lambda Expressions

The lambda expression was introduced first time in Java 8. Its main objective to increase the expressive power of the language.

Lambda expression is, essentially, an anonymous or unnamed method. The lambda expression does not execute on its own. Instead, it is used to implement a method defined by a functional interface.

Syntax

(parameter list) -> lambda body

The new operator (->) used is known as an arrow operator or a lambda operator. The syntax might not be clear at the moment. Let's explore some examples,

Suppose, we have a method like this:

double getPiValue() {
    return 3.1415;
}

We can write this method using lambda expression as:

() -> 3.1415

Here, the method does not have any parameters. Hence, the left side of the operator includes an empty parameter. The right side is the lambda body that specifies the action of the lambda expression. In this case, it returns the value 3.1415.

Types of Lambda Body

1. A body with a single expression

() -> System.out.println("Lambdas are great");

2. A body that consists of a block of code.

() -> {
    double pi = 3.1415;
    return pi;
};

This type of the lambda body is known as a block body. The block body allows the lambda body to include multiple statements. These statements are enclosed inside the braces and you have to add a semi-colon after the braces.

Note: For the block body, you can have a return statement if the body returns a value. However, the expression body does not require a return statement.

Lambda Expressions with parameters

Till now we have created lambda expressions without any parameters. However, similar to methods, lambda expressions can also have parameters. For example,

(n) -> (n%2)==0

Here, the variable n inside the parenthesis is a parameter passed to the lambda expression. The lambda body takes the parameter and checks if it is even or odd.

Example

Let's write A block lambda that computes the factorial of an int value.

As mentioned earlier, a lambda expression is not executed on its own. Rather, it forms the implementation of the abstract method defined by the functional interface.

interface NumericFunc {
 int func(int n);
}
class BlockLambdaDemo {
 public static void main(String[] args)
 {
 // This block lambda computes the factorial of an int value.
 NumericFunc factorial = (n) -> {
 int result = 1;
 for(int i=1; i <= n; i++)
 result = i * result;
 return result;
 };
 System.out.println("The factoral of 3 is " + factorial.func(3));
 System.out.println("The factoral of 5 is " + factorial.func(5));
 }
}

Output

The factorial of 3 is 6
The factorial of 5 is 120

Generic Functional Interfaces

Till now we have used the functional interface that accepts only one type of value. However, we can make the functional interface generic, so that any data type is accepted.

Example

Let's write a program on Generic Functional Interface and Lambda Expressions which uses one single interface and calculates factorial of a number as well as reverses a string.

interface SomeFunc<T> {
 T func(T t);
}
class GenericFunctionalInterfaceDemo {
 public static void main(String[] args)
 {
 // Use a String-based version of SomeFunc.
 SomeFunc<String> reverse = (str) -> {
 String result = "";
 int i;
 for(i = str.length()-1; i >= 0; i--)
 result += str.charAt(i);
 return result;
 };

 System.out.println("Lambda reversed is " +
 reverse.func("Lambda"));
 System.out.println("Expression reversed is " +
 reverse.func("Expression"));

 // Now, use an Integer-based version of SomeFunc.
 SomeFunc<Integer> factorial = (n) -> {
 int result = 1;
 for(int i=1; i <= n; i++)
 result = i * result;
 return result;
 };

 System.out.println("The factoral of 3 is " + factorial.func(3));
 System.out.println("The factoral of 5 is " + factorial.func(5));
 }
}

Output

Lambda reversed is adbmaL
Expression reversed is noisserpxE
The factoral of 3 is 6
The factoral of 5 is 120

The SomeFunc interface is used to provide a reference to two different types of lambdas. The first uses type String. The second uses type Integer. Thus, the same functional interface can be used to refer to the reverse lambda and the factorial lambda. Only the type argument passed to SomeFunc differs.

Lambda Expressions to run thread

You can use lambda expression to run thread. In the following example, we are implementing run method by using lambda expression.

public class LambdaExpressionExample9{  
    public static void main(String[] args) {  
      
        //Thread Example without lambda  
        Runnable r1=new Runnable(){  
            public void run(){  
                System.out.println("Thread1 is running...");  
            }  
        };  
        Thread t1=new Thread(r1);  
        t1.start();  
        //Thread Example with lambda  
        Runnable r2=()->{  
                System.out.println("Thread2 is running...");  
        };  
        Thread t2=new Thread(r2);  
        t2.start();  
    }  
}  

Passing Lambda Expressions as Arguments

To pass a lambda expression as an argument, the type of the parameter receiving the lambda expression argument must be of a functional interface type compatible with the lambda. Although using a lambda expression as an argument is straightforward, it is still helpful to see it in action.

Example

Convert an ArrayList to uppercase using replaceAll method of ArrayList and passing lambda expression to it

import java.util.ArrayList;

class Main {
    public static void main(String[] args) {
        // create an ArrayList
        ArrayList<String> languages = new ArrayList<>();

        // add elements to the ArrayList
        languages.add("java");
        languages.add("swift");
        languages.add("python");
        System.out.println("ArrayList: " + languages);

        // pass lambda expression as parameter to replaceAll() method
        languages.replaceAll(e -> e.toUpperCase());
        System.out.println("Updated ArrayList: " + languages);
    }
}

Output

ArrayList: [java, swift, python]
Updated ArrayList: [JAVA, SWIFT, PYTHON]

Lambda Expressions and Exceptions

A lambda expression can throw an exception. However, it if throws a checked exception, then that exception must be compatible with the exception(s) listed in the throws clause of the abstract method in the functional interface.

Example

It computes the average of an array of double values. If a zero-length array is passed, however, it throws the custom exception EmptyArrayException.

interface DoubleNumericArrayFunc {
 double func(double[] n) throws EmptyArrayException;
}
class EmptyArrayException extends Exception {
 EmptyArrayException() {
 super("Array Empty");
 }
}
class LambdaExceptionDemo {
 public static void main(String[] args) throws EmptyArrayException
 {
 double[] values = { 1.0, 2.0, 3.0, 4.0 };
 // This block lambda computes the average of an array of doubles.

 DoubleNumericArrayFunc average = (n) -> {
 double sum = 0;
 if(n.length == 0)
 throw new EmptyArrayException();//exception in a lambda block
 for(int i=0; i < n.length; i++)
 sum += n[i];
 return sum / n.length;
 };


 System.out.println("The average is " + average.func(values));
 // This causes an exception to be thrown.
 System.out.println("The average is " + average.func(new double[0]));
 }
}

Remember, the inclusion of the throws clause in func( ) is necessary. Without it, the program will not compile because the lambda expression will no longer be compatible with func( ).

Exercises

Exercise 1

Write a Java program that demonstrates the use of block lambda expressions to filter a list of strings based on their length. The program should utilize a functional interface

interface StringFilter {
    List<String> filter(List<String> strings, int minLength);
}

which filters strings from a given list based on a minimum length, i.e. only strings with length >= minimum length are filtered and printed.

Sample Input

List<String> strings = new ArrayList<>();
strings.add("apple");
strings.add("banana");
strings.add("orange");
strings.add("kiwi");
strings.add("grape");
int minLength = 6;

Sample Output

Filtered list: [banana, orange]

Hint : create a new list inside the block lambda expression, check for length and add filtered strings to it, then return the list and store it outside during the method call. Then print it.

Exercise 2

Enhance the provided Java program to calculate the factorial of an integer and reverse a string concurrently using threads, while implementing their run methods using lambda expressions only. The program should utilize a single generic interface SomeFunc to represent both operations. Create two different threads for both operations and then start them.

Exercise 3

Write a Java program that allows dividing two numbers of integer, double, or float data types. The program should take the data type from the user, apply conditional statements to implement a generic interface using lambda expressions, and handle the scenario where the second number is zero by throwing an Arithmetic exception.

Sample Inputs

Enter the data type (integer, double, or float): float
Enter the first float :12.5
Enter the second float :2.5
Enter the data type (integer, double, or float): double
Enter the first double :100.0
Enter the second double :0

Sample Outputs

Result of division: 5.0
Result of division: Arithmetic Exception, cannot divide by zero

Last updated