1

I am currently trying to understand how Java infers the type of lambda expressions. I can illustrate with an example:

Writing:

producer.send(record, new Callback() {
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null) {
                        logger.info("Received new metadata"
                        );
                    } else {
                        logger.error("Error while producing " + exception);

My IDE suggested that can be re-written to:

producer.send(record, (metadata, exception) -> {
    if (exception == null) {
        logger.info("Received new metadata"
        );
    } else {
        logger.error("Error while producing " + exception);
    }
     

Which made me think: How does the compiler guess the types for metadata and exception?

Reading through some articles like this, I found that:

Java 8 also introduced Lambda Expressions. Lambda Expressions do not have an explicit type. Their type is inferred by looking at the target type of the context or situation. The Target-Type of an expression is the data type that the Java Compiler expects depending on where the expression appears.

I am not sure what is meant here by "context or situation". I am looking for a better technical explanation of how the compiler infers types. And when would I need to explicitly tag types.

2
  • To get the full picture you will have to look it up in the JLS. Commented Jul 15, 2020 at 17:53
  • 1
    A lambda can only exist in a context. The context is typically "the left side" to the lambda, like Runnable foo = in Runnable foo = () -> System.out.println("hello");. Or, in your case, what the method send expects. In any case, a lambda is always subject to some interface. You can only use a lambda in a context where an interface is expected. The lambda will then basically create some kind of anonymous instance of this interface. Hence the types are determined by the expected interface. (the interface must be functional, so only contain one to-be-implemented method) Commented Jul 15, 2020 at 18:51

2 Answers 2

5

producer.send is a method that accepts a record and a Callback, and Callback has exactly one abstract method, which accepts a RecordMetadata and an Exception. Therefore, if the compiler sees a lambda as the second argument to producer.send, it must be implementing the method Callback.onCompletion, and it must have two arguments, with the first a RecordMetadata and the second an Exception.

The point being: it's inferred from the type of the method that you're passing the lambda to.

Sign up to request clarification or add additional context in comments.

1 Comment

"it's inferred from the type of the method that you're passing the lambda to" perfect answer
1

Adding a point to Louis Wasserman's answer.

The same lambda expression can be applied to different target types.

private static void predicate(Predicate<String> predicate) {
    predicate.test("abcd");
}

private static void function(Function<String, Boolean> function) {
    function.apply("abcd");
}


predicate(s -> s.length() > 5);
function(s -> s.length() > 5);     

The lambda expression s -> s.length() > 5 can both be a Predicate and a Function based on the context.

Considering method references,

private static void consumer(Consumer<String> consumer) {
    consumer.accept("abcd");
}

private static void function2(Function<String, Integer> function) {
    function.apply("abcd");
}
 
consumer(String::length);
function2(String::length);

You might be surprised to find that we can use String::length as a Consumer. Think of it as ignoring the return type. But expanding the method reference won't work

consumer(s -> {
    return s.length();
});

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.