Wednesday, December 21, 2022

Quiz yourself: Lambda expressions and local variables in Java


Given the following Capture class

Oralce Java, Java Exam Prep, Java Tutorial and Materials, Java Certification, Java Learning, Java Prep, Java Preparation, Oracle Java Lambda

import java.util.function.Supplier;
public class Capture {
    public static void main(String[] args) {
        System.out.println(supp1().get() + supp2().get());
    }
    static Supplier<String> supp1() {
        var val = "Supp 1 ";
        Supplier<String> s = (() -> { return val; });
        val = "Supp 1 New ";
        return s;
    }
    static Supplier<String> supp2() {
        var val = new StringBuilder("Supp 2 ");
        Supplier<String> s = (() -> { return val.toString(); });
        val.append("New");
        return s;
    }
}

What is the result? Choose one.

A. Supp 1 Supp 2 is the result.
B. Supp 1 Supp 2 New is the result.
C. Supp 1 New Supp 2 New is the result.
D. Compilation fails due to supp1() method.
E. Compilation fails due to supp2() method.

Answer. This question covers several topics including implementing the Supplier interface, the syntax of lambda expressions, the concept and syntax of a closure, and mutation of values in closures. As is often the case with seemingly complex questions in an exam, only one of these topics is crucial to finding the correct answer.

The exam question has two options that suggest that the code might fail to compile, so you need to bear that in mind, because if compilation fails, there is no output.

First, in the main method, notice that the code invokes the get() methods of the objects returned from two functions: supp1() and supp2(). These functions both return Supplier<String>, which is a functional interface with the single abstract method String get(). So, at this point, it seems that the output, if any, would in fact be the concatenation of the text returned by those two Supplier objects.

Next, consider the bodies of the supp1() and supp2() methods. Both return a lambda expression, and broadly speaking, the lambdas are structurally correct for implementing the Supplier<String> interface. Both lambdas take zero arguments, and they both return a String, which makes them consistent with the signature of the get() method required for a Supplier<String>.

On closer inspection, you should notice that both lambdas use variables that are local to their surrounding method. Of course, the lifetime of a method’s local variable is normally limited to the duration of that method’s execution, but the objects created by the lambda have lifetimes that exceed that; they are objects, so they exist at least as long as they are reachable.

Because the objects represented by the lambdas are returned from the methods, it’s guaranteed that their lifetime exceeds that of a normal local variable in the method that creates them. That raises a philosophical question: What is the value of a variable that doesn’t exist? That is, if the lambda is executed after the return from the method that created that lambda, what value will be found for the local variable enclosed in the lambda?

Java provides a simple solution to this: In effect, Java embeds a copy of the value of the local variable into the object represented by the lambda. (Note that this discussion doesn’t necessarily reflect how the implementation is performed but describes the effect as seen from the source language. Lambdas have several different approaches to efficient implementation.)

Well, a copy of a variable is somewhat dangerous, because if either of the copies were to change, the two become inconsistent, so which should now be considered valid? Java solves this by insisting that any variable that’s used in this way must never be changed. (Prior to Java 8, such a variable could be used in an anonymous inner class provided it was marked final. Beginning with Java 8 and the advent of lambda syntax, it’s sufficient that the variable be effectively final, that is, the variable must never be reassigned.)

In the case of supp2(), the variable val is, in fact, not reassigned. The object to which it refers (a StringBuilder) is mutated, but that’s not the same as reassigning the reference variable in the variable itself. So, this method is valid, and it would return the text Supp 2 New if the resulting Supplier were accessed.

However, in the case of supp1(), the variable val (the one referred to in the lambda) is reassigned. This is prohibited, and the code for supp1() would not compile. Based on this observation, you can see that option D is correct, and options A, B, C, and E must be incorrect.

Here are a couple of side notes.

First, the idea of capturing a local variable into a lambda expression (or anonymous inner object) is often referred to—in Java and other languages—as a closure. Some people object to the restriction that the value must never be changed, but in practice this is not really a problem because you can always modify the code to use a final reference to a mutable data item, as shown in supp2(). If you need to use a primitive value, simply using an array of a single element solves the problem.

Second, some people feel the requirement for final or effectively final values in closures results in something that is not really a valid closure. However, keep in mind that a closure is primarily a functional programming concept, and in the purest forms of functional programming, all data is immutable anyway. Indeed, that kind of side effect is prone to creating difficult-to-identify bugs.

Conclusion. The correct answer is option D.

Source: oracle.com

Related Posts

0 comments:

Post a Comment