Lambda Functions in Java and Custom Functional Interfaces

In this article, we will learn all about lambda functions in Java and Custom Functional Interfaces with examples. It is good to learn lambda declarations with custom functional interfaces together. Let’s create a custom interface as PlayMusicService which has a single abstract run method.

//SAM: Single Abstract Method (Functional Interface annotation dictates that this interface has single abstract method.)
@FunctionalInterface
public interface PlayMusicService {
    void run(String song);
}

Now, let’s implement this run method in a test class in two ways, the classical way with @override annotation and the more modern way with a lambda function declaration.

public class PlayMusicServiceTest {
    @Test
    public void playMusicWithoutLambdaTest(){
        PlayMusicService playIt = new PlayMusicService() {
            @Override public void run(String song) {
                System.out.println("First playing: "+ song);
            }
        };

        playIt.run("Despacito");
    }

    @Test
    public void playMusicWithLambdaTest(){
        PlayMusicService playIt = (song) -> System.out.println("First playing: "+ song); //lambda declaration.

        playIt.run("Despacito");
    }
}

As you can see, with starting Java 8, lambda declarations make our programming life better and neater. We can implement the same functionality with less code. Lambdas simplify the long @override way of implementation in this way.

new PlayMusicService() {
@Override public void run(String song) { 

is transformed as

(song) ->

and inside of the run method is the same for both implementations.

System.out.println(“First playing: “+ song);

Let’s run our test and see the result.

lambda functions in java

Lamda Functions as a Parameter in Java

One of the powerful features of functional programming is we can define lambda functions as parameters and send them to the functions as arguments. I want to show this with an example. Let’s assume we have PlayService and it has a run method that we can implement.

//SAM: Single Abstract Method (Functional Interface annotation dictates that this interface has single abstract method.)
@FunctionalInterface
public interface PlayService {
    void run(String name, double duration, boolean repeat);
}

Now, we will create a test class and in this class, we have testPlay() method. In this method, we will send a lambda function as an argument.

public class PlayTest {
    public static void main(String[] args) {
        //Function definition
        PlayService playIt = (name, duration, repeat) ->
            System.out.println(name.toUpperCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n");

        //Sending the lambda function as an argument.
        testPlay(playIt);

        //Function calling by sending the function as an argument as lambda declaration.
        testPlay((name, duration, repeat) ->
            System.out.println(name.toLowerCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n"));
    }

    //Declaring lambda function as a parameter.
    private static void testPlay(PlayService playService) {
        System.out.println("This is a song play test!");
        playService.run("Despacito", 3.20, true);

        System.out.println("This is a video play test!");
        playService.run("Messi Skills", 4.20, true);
    }
}

As you can see above code, our testPlay(PlayService playService) method has a parameter as lambda function and in the main class we declare this lambda function as:

//Function definition
PlayService playIt = (name, duration, repeat) ->
    System.out.println(name.toUpperCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n");

and call the testPlay() method by sending the playIt lambda function as an argument.

//Function calling by sending the function as an argument.
testPlay(playIt);

We can even directly use the lambda declaration inside the testPlay method’s parameter section.

//Function calling by sending the function as an argument as lambda declaration.
testPlay((name, duration, repeat) ->
    System.out.println(name.toLowerCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n"));

Let’s run the test and see the results.

function as a parameter in java

Here, we call testPlay() method two times, for the first one we send a lambda function that has an upper-case functionality, and the second one has a lower-case functionality.

Let’s do one more example. Assume that we have a shopping cart and we have a CartAction interface with the apply method.

@FunctionalInterface
public interface CartAction {
    void apply(String message, boolean isExist);
}

We will implement this apply method with several lambda functions like addItem, deleteItem, deleteCart. Also, we have an action method that takes the CartAction lambda function as a parameter. Finally, we will write two tests one will be an example of sending lambda functions as an argument and the other one is directly use the lambda functions. Here is the code.

public class ShoppingCartTest {
    int itemCount = 0;

    CartAction addItem = (message, status) -> {
        itemCount++;
        System.out.println(message + " | Item count in the cart is: " + itemCount + " | Cart existence: " + status);
    };

    CartAction deleteItem = (message, status) -> {
        itemCount--;
        System.out.println(message + " | Item count in the cart is: " + itemCount + " | Cart existence: " + status);
    };

    CartAction deleteCart = (message, status) -> {
        itemCount = 0;
        System.out.println(message + " | Item count in the cart is: " + itemCount + " | Cart existence: " + status);
    };

    private void action(CartAction action, String message, boolean status) {
        action.apply(message, status);
    }

    //Sending lambda functions as an argument version
    @Test
    public void shoppingCartTest1() {
        System.out.println("Shopping Cart Test 1! Function as parameter!");
        action(addItem, "Item has been added!", true);
        action(addItem, "Item has been added!", true);
        action(addItem, "Item has been added!", true);
        action(deleteItem, "Item has been deleted!", true);
        action(deleteItem, "Item has been deleted!", true);
        action(deleteCart, "Cart has been deleted!", false);
        action(addItem, "Item has been added!", true);
        action(deleteItem, "Item has been deleted!", true);
        action(addItem, "Item has been added!", true);
        action(addItem, "Item has been added!", true);
        action(deleteCart, "Cart has been deleted!", false);
        System.out.println("--------------------------------------------");
    }

    //Directly calling lambda functions
    @Test
    public void shoppingCartTest2() {
        System.out.println("Shopping Cart Test 2! Direct lambda function call!");
        addItem.apply("Item has been added!", true);
        addItem.apply( "Item has been added!", true);
        addItem.apply( "Item has been added!", true);
        deleteItem.apply("Item has been deleted!", true);
        deleteItem.apply("Item has been deleted!", true);
        deleteCart.apply("Cart has been deleted!", false);
        addItem.apply("Item has been added!", true);
        deleteItem.apply("Item has been deleted!", true);
        addItem.apply("Item has been added!", true);
        addItem.apply("Item has been added!", true);
        deleteCart.apply("Cart has been deleted!", false);
        System.out.println("--------------------------------------------");
    }

The output of the test is shown below.

lambdas in java

Lambda Functions with Method References

We can declare the lambda functions with method references. For this we use a double colon :: 

Let’s see this with an example!

public class MethodReferencesExample {
    @Test
    public void lambdaWithoutMethodReference(){
        //Function definition with lambda without method reference
        PrintService printUpperCase = text -> text.toUpperCase();

        //Function call
        String upperCaseText = printUpperCase.apply("software test academy");

        //Print the result
        System.out.println(upperCaseText);
    }

    @Test
    public void lambdaWithMethodReference(){
        //Function definition with lambda with method reference
        PrintService printUpperCase = String::toUpperCase;

        //Function call
        String upperCaseText = printUpperCase.apply("software test academy");

        //Print the result
        System.out.println(upperCaseText);
    }
}

java method references

GitHub Project

https://github.com/swtestacademy/java-functional/tree/main/src/test/java/functional/lambda

Thanks for reading,
Onur

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.