How do lambda calls interact with Interfaces?

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.