Mastering Lambda Expressions in Java: A Guide to Simplified Functional Programming

Lambda Expressions

Lambda Expressions

  • Lambda expressions were introduced in Java 8 and are a key feature for supporting functional programming.
  • Java, up to version 7, did not support functional programming. However, from Java 8 onward, it introduced support for functional interfaces.
  • Lambda expressions are most suitable for providing implementations for functional interfaces or single abstract method interfaces.
  • They offer a concise and clear way to implement functional interfaces and are widely used in the Java Collections Framework for operations like iteration, filtering, and extracting data.

Functional Interface

  • A functional interface is an interface that contains exactly one abstract method.
  • To designate an interface as a functional interface, Java provides the @FunctionalInterface annotation.

How to Provide Interface Implementation (Up to Java 7)

1. Using an Implementation Class

interface Test {
    public abstract void m1();
}

class Check implements Test {
    @Override
    public void m1() {
        System.out.println("This is the implementation method for Test interface...");
    }
}

public class Lambda {
    public static void main(String[] args) {
        Test t = new Check();
        t.m1();
    }
}
    

2. Using Anonymous Inner Class for Single Method Interface

interface Test {
    public abstract void m1();
}

public class Lambda {
    public static void main(String[] args) {
        Test t = new Test() {
            public void m1() {
                System.out.println("This is the implementation for Test interface...");
            }
        };
        t.m1();
    }
}
    

3. Using Anonymous Inner Class for Multiple Method Interface

interface Test {
    public abstract void m1();
    public abstract void m2();
}

public class Lambda {
    public static void main(String[] args) {
        Test t = new Test() {
            public void m1() {
                System.out.println("This is the implementation for Test interface...m1()");
            }
            public void m2() {
                System.out.println("This is the implementation for Test interface...m2()");
            }
        };
        t.m1();
        t.m2();
    }
}
   

In the above program we are getting bellow .class files

a.  Test.class

b.  Lambda.class

c.  Lambda$1.class

Every anonymous class is the sub or implementation class for either class or interface.


4. From Java 8 Onwards, We Have a Powerful Tool: LAMBDA EXPRESSION

Lambda expressions are suitable for Single Abstract Method Interfaces (Functional Interfaces).

Example: Using Lambda Expression
interface Test {
    public abstract void m1();
}

public class Lambda {
    public static void main(String[] args) {
        Test t = () -> {
            System.out.println("This is the implementation for Test interface"
                               + " by using lambda");
        };
        t.m1();
    }
}
    

In the above example, we are only getting two .class files:

  • Test.class
  • Lambda.class

No Lambda$1.class file is generated, indicating that we are not creating any additional classes. This showcases the functional programming aspect of Java.

Anonymous Inner Class Example with Zero Arguments
interface Square {
    public static final int side = 50;
    public abstract void area();
}

public class Lambda {
    public static void main(String[] args) {
        Square t = new Square() {
            public void area() {
                System.out.println("The area of the square is:: " + (4 * side));
            }
        };
        t.area();
    }
}
    

In the above program, the side variable is not redefined in the anonymous inner class but is taken from the interface.

Lambda Expression for Single Abstract Method Interface (Zero Argument Method)

Here’s an example of a lambda expression for a functional interface with a zero argument or non-parameterized method:

interface Square {
    public static final int side = 50;
    public abstract void area();
}

public class Lambda {
    public static void main(String[] args) {
        Square t = () -> {
            System.out.println("The area of the square is:: " + (4 * side));
        };
        t.area();
    }
}
    

Compile-time Error: In the above code, we get a compile-time error because the lambda expression cannot access the side variable from the Square interface. This is because lambda expressions are not considered implementation classes.

Solution: Using a Local Variable

To resolve the issue, declare the side variable as a local variable inside the main method:

interface Square {
    public abstract void area();
}

public class Lambda {
    public static void main(String[] args) {
        int side = 500;
        Square t = () -> {
            System.out.println("The area of the square is:: " + (4 * side));
        };
        t.area();
    }
}
    
Using Static Variables in Lambda Expressions

In the following example, the side variable is taken from the local scope of the main method. We can also declare side as a static variable at the class level:

interface Square {
    public abstract void area();
}

public class Lambda {
    static int side = 14;

    public static void main(String[] args) {
        Square t = () -> {
            System.out.println("The area of the square is:: " + (4 * side));
        };
        t.area();
    }
}
    

In the above example, the side variable is declared as a static variable, allowing the lambda expression to access it from within a static context.

Non-Static Variables in Lambda Expressions

However, if we declare side as a non-static (instance) variable, it will result in a compile-time error because non-static variables cannot be accessed directly from a static context:

interface Square {
    public abstract void area();
}

public class Lambda {
    int side = 14;

    public static void main(String[] args) {
        Square t = () -> {
            System.out.println("The area of the square is:: " + (4 * side));
        };
        t.area();
    }
}
    

Compile-time Error: The above code will not compile because a non-static variable cannot be referenced from a static context.

Lambda Expression with Argument Method - Square Example

In the following example, we define a functional interface Square that contains a method with an argument. The lambda expression provides the implementation:

interface Square {
    public abstract void area(int side);
}

public class Lambda {
    public static void main(String[] args) {
        Square t = (side) -> {
            System.out.println("The area of the square is:: " + (4 * side));
        };
        t.area(111);
    }
}
    

The lambda expression calculates the area of the square based on the given side length.

Lambda Expression with Argument Method - Rectangle Example

Here, we extend the concept by implementing a functional interface Rectangle that accepts two arguments:

interface Rectangle {
    public abstract void area(int length, int breadth);
}

public class Lambda {
    public static void main(String[] args) {
        Rectangle t = (a, b) -> {
            System.out.println("The area of the rectangle is:: " + (a * b));
        };
        t.area(10, 20);
    }
}
    

This example calculates the area of a rectangle given its length and breadth as input.

Lambda Expression with Non-Void Method (Returning Values)

In Java, if a method is non-void, it must end with a return statement. However, in lambda expressions, Java allows us to omit the return keyword for single-line expressions:

interface Rectangle {
    public abstract int area(int length, int breadth);
}

public class Lambda {
    public static void main(String[] args) {
        Rectangle t = (a, b) -> a * b;
        System.out.println(t.area(100, 200));  // Output: 20000
        
        Rectangle t1 = (a, b) -> 2 * (a + b);
        System.out.println(t1.area(10, 20));  // Output: 60
    }
}
    

In this case, the lambda expressions return the result of the calculation. For multi-line bodies, we need to use the return keyword:

Rectangle t1 = (a, b) -> {
    return 2 * (a + b);
};
System.out.println(t1.area(10, 20));  // Output: 60
    

If we wrap the expression in curly braces, we must explicitly use the return keyword as shown above.

Tracing Collection Object Elements - With Lambda Expression

Simplifies the code, making it easier to read and maintain by using the forEach method.

import java.util.ArrayList;
import java.util.List;

public class Lambda {
    public static void main(String[] s) {
        List l = new ArrayList();
        l.add("nit");
        l.add("kit");
        l.add("nacre");
        l.add("ram");
        l.add("cj");
        l.add("aj");

        l.forEach(data -> {
            System.out.println(data);
        });
    }
}
    

Program on Lambda Expression with Multiple Statements

interface Rectangle {
    public abstract void test(int length, int breadth);
}

public class Lambda {
    public static void main(String[] args) {
        Rectangle s = (length, breadth) -> {
            int perimeter = 2 * (length + breadth);
            System.out.println("Perimeter: " + perimeter);
            int area = length * breadth;
            System.out.println("Area: " + area);
        };
        s.test(100, 200);
    }
}
    

This program defines a functional interface called Rectangle with an abstract method test. In the main method, a lambda expression is implemented to calculate both the perimeter and area of a rectangle. The body of the lambda contains multiple statements, allowing for complex logic.

How to Create a Thread Using Lambda Expression

public class Lambda {
    public static void main(String[] args) {
        Runnable r = () -> {
            System.out.println("This is a thread: Lambda Expression");
        };

        Thread t = new Thread(r);
        t.start();
    }
}
    

Sorting Collection Elements Without Using a Comparator Class

Simplifies the code, making it easier to read and maintain by using the forEach method.

import java.util.Set;
import java.util.TreeSet;

class Student {
    int sid;
    String sname;
    Integer sage;

    public Student(int sid, String sname, int sage) {
        this.sid = sid;
        this.sname = sname;
        this.sage = sage;
    }

    @Override
    public String toString() {
        return sid + "..." + sname + "..." + sage;
    }
}

public class Lambda {
    public static void main(String[] args) {
        Student s1 = new Student(101, "ram", 30);
        Student s2 = new Student(102, "sam", 20);
        Student s3 = new Student(103, "varun", 40);
        Student s4 = new Student(104, "kiran", 50);
        Student s5 = new Student(105, "uma", 10);

        // Using TreeSet with a lambda expression for sorting by age in descending order
        Set students = new TreeSet<>((s1, s2) -> s2.sage.compareTo(s1.sage));

        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        students.add(s5);

        // Iterate and print sorted students
        for (Student student : students) {
            System.out.println(student);
        }

        System.out.println("Program finished");
    }
}
    

The TreeSet is initialized with a lambda expression that sorts Student objects by age in descending order.

Lambda Expression for Sorting Student Objects by Name

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Student {
    int sid;
    String sname;
    int sage;

    public Student(int sid, String sname, int sage) {
        this.sid = sid;
        this.sname = sname;
        this.sage = sage;
    }

    @Override
    public String toString() {
        return sid + "..." + sname + "..." + sage;
    }
}

public class Lambda {
    public static void main(String[] args) {
        Student s1 = new Student(101, "ram", 30);
        Student s2 = new Student(102, "sam", 20);
        Student s3 = new Student(103, "varun", 40);
        Student s4 = new Student(104, "kiran", 50);
        Student s5 = new Student(105, "uma", 10);

        List students = new ArrayList<>();
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        students.add(s5);

        // Sorting the list of students by name using a lambda expression
        Collections.sort(students, (p1, p2) -> p1.sname.compareTo(p2.sname));

        // Printing the sorted students
        students.forEach(data -> System.out.println(data));
        
        System.out.println("Program finished");
    }
}
    

Comments

Popular posts from this blog

Java 8: A Game-Changer for Developers

Java2Web Blogger Page