Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do lambda calls interact with Interfaces?

The code snippet shown below works. However, I'm not sure why it works. I'm not quite following the logic of how the lambda function is passing information to the interface.

Where is control being passed? How is the compiler making sense of each n in the loop and each message created?

This code compiles and gives the expected results. I'm just not sure how.

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

public class TesterClass {

    public static void main(String[] args) {

        List<String> names = new ArrayList<>();

        names.add("Akira");
        names.add("Jacky");
        names.add("Sarah");
        names.add("Wolf");

        names.forEach((n) -> {
            SayHello hello = (message) -> System.out.println("Hello " + message);
            hello.speak(n);
        });
    }

    interface SayHello {
        void speak(String message);
    }
}
like image 273
Brodiman Avatar asked Nov 03 '19 02:11

Brodiman


People also ask

How does lambda expression and functional interface work together?

A functional interface is an interface that contains only one abstract method. They can have only one functionality to exhibit. From Java 8 onwards, lambda expressions can be used to represent the instance of a functional interface. A functional interface can have any number of default methods.

Can we use lambda expression in interface?

The Lambda expression is used to provide the implementation of an interface which has functional interface. It saves a lot of code. In case of lambda expression, we don't need to define the method again for providing the implementation. Here, we just write the implementation code.

Which interface is used in lambda expression?

Approach 6: Use Standard Functional Interfaces with Lambda Expressions. This is a very simple interface. It's a functional interface because it contains only one abstract method. This method takes one parameter and returns a boolean value.

Why do I need a functional interface to work with lambdas?

Runnable is a functional interface, that's why we can use lambda expression to create it's instance. Since run() method takes no argument, our lambda expression also have no argument. Just like if-else blocks, we can avoid curly braces ({}) since we have a single statement in the method body.


1 Answers

The SayHello is a Single Abstract Method interface which has a method that takes a string and returns void. This is analogous to a consumer. You are just providing an implementation of that method in form of a consumer which is similar to the following anonymous inner class implementation.

SayHello sh = new SayHello() {
    @Override
    public void speak(String message) {
        System.out.println("Hello " + message);
    }
};

names.forEach(n -> sh.speak(n));

Fortunately, your interface has a single method such that the type system (more formally, the type resolution algorithm) can infer its type as SayHello. But if it were to have 2 or more methods, this would not be possible.

However, a much better approach is to declare the consumer before the for-loop and use it as shown below. Declaring the implementation for each iteration creates more objects than necessary and seems counter-intuitive to me. Here's the enhanced version using method references instead of lambda. The bounded method reference used here calls the relevant method on the hello instance declared above.

SayHello hello = message -> System.out.println("Hello " + message);
names.forEach(hello::speak);

Update

Given that for stateless lambdas that does not capture anything from their lexical scope only once instance will ever be created, both of the approaches merely create one instance of the SayHello, and there's no any gain following the suggested approach. However this seems to be an implementation detail and I didn't know it until now. So a much better approach is just to pass in a consumer to your forEach as suggested in the comment below. Also note that all these approaches creates just one instance of your SayHello interface while the last one is more succinct. Here's how it looks.

names.forEach(message -> System.out.println("Hello " + message));

This answer will give you more insight on that. Here's the relevant section from the JLS §15.27.4: Run-Time Evaluation of Lambda Expressions

These rules are meant to offer flexibility to implementations of the Java programming language, in that:

  • A new object need not be allocated on every evaluation.

In fact, I initially thought every evaluation creates a new instance, which is wrong. @Holger thanks for pointing that out, good catch.

like image 99
Ravindra Ranwala Avatar answered Oct 20 '22 12:10

Ravindra Ranwala