Sunday, September 22, 2019

Lambda Expression Java 8 - Interview Questions

1. for (int i = 0; i < n; i++) {
    new Thread(() -> System.out.println(i)).start();
        // Error—cannot capture i
}
The lambda expression tries to capture i, but this is not legal because i changes. There is no single value to capture. The rule is that a lambda expression can only access local variables from an enclosing scope that are effectively final. An effectively final variable is never modified—it either is or could be declared as final.

2. http://www.lambdafaq.org/what-are-the-reasons-for-the-restriction-to-effective-immutability/

3. Why the restriction on local variable capture?
Capture of local variables is restricted to those that are effectively final. Lifting this restriction would present implementation difficulties, but it would also be undesirable; its presence prevents the introduction of a new class of multithreading bugs involving local variables. Local variables in Java have until now been immune to race conditions and visibility problems because they are accessible only to the thread executing the method in which they are declared. But a lambda can be passed from the thread that created it to a different thread, and that immunity would therefore be lost if the lambda, evaluated by the second thread, were given the ability to mutate local variables. Even the ability to read the value of mutable local variables from a different thread would introduce the necessity for synchronization or the use of volatile in order to avoid reading stale data.

An alternative way to view this restriction is to consider the use cases that it discourages. Mutating local variables in idioms like this:

int sum = 0;
list.forEach(e -> { sum += e.size(); }); // illegal; local variable 'sum' is not effectively final
frustrates a principal purpose of introducing lambdas. The major advantage of passing a function to the forEach method is that it allows strategies that distribute evaluation of the function for different arguments to different threads. The advantage of that is lost if these threads have to be synchronized to avoid reading stale values of the captured variable.

The restriction of capture to effectively immutable variables is intended to direct developers’ attention to more easily parallelizable, naturally thread-safe techniques. For example, in contrast to the accumulation idiom above, the statement

int sum = list.map(e -> e.size()).reduce(0, (a, b) -> a+b);
creates a pipeline in which the results of the evaluations of the map method can much more easily be executed in parallel, and subsequently gathered together by the reduce operation.

The restriction on local variables helps to direct developers using lambdas aways from idioms involving mutation; it does not prevent them. Mutable fields are always a potential source of concurrency problems if sharing is not properly managed; disallowing field capture by lambda expressions would reduce their usefulness without doing anything to solve this general problem.

4. What is the type of a lambda expression?
A lambda expression is an instance of a functional interface. But a lambda expression itself does not contain the information about which functional interface it is implementing; that information is deduced from the context in which it is used. For example, the expression

    x -> 2 * x
can be an instance of the functional interface

    interface IntOperation { int operate(int i); }
so it is legal to write

    IntOperation iop = x -> x * 2;
The type expected for the expression on the right-hand side of the assignment here is IntOperation. This is called the target type for the lambda expression. Clearly a lambda expression can be type-compatible with different functional interfaces, so it follows that the same lambda expression can have different target types in different contexts. For example, given an interface

    interface DoubleOperation { double operate(double i); }
it would also be legal to write

    DoubleOperation dop = x -> x * 2;
The target type for a lambda expression must be a functional interface and, to be compatible with the target type, the lambda expression must have the same parameter types as the interface’s function type, its return type must be compatible with the function type, and it can throw only exceptions allowed by the function type.

From Java 8 onwards, lambda expressions can be used to represent the instance of a functional interface.


5. What is a functional interface?
Informally, a functional interface is one whose type can be used for a method parameter when a lambda is to be supplied as the actual argument. For example, the forEach method on collections could have the following signature:

    public void forEach(Consumer<? super T> consumer);
The implementation of forEach must apply a single method of the Consumer instance that has been supplied. This instance may be a lambda expression (see What is the type of a lambda expression?); if so, it will be applied in the place of that method. A lambda expression supplied in this way can take the place of only one interface method, so an interface can be used like this without ambiguity only if it has a single method.

More precisely, a functional interface is defined as any interface that has exactly one explicitly declared abstract method. (The qualification is necessary because an interface may have non-abstract default methods.) This is why functional interfaces used to be called Single Abstract Method (SAM) interfaces, a term that is still sometimes seen.

Examples
The following interfaces in the platform libraries (chosen from many) are functional interfaces according to the definition above:

    public interface Runnable { void run(); }
    public interface Callable<V> { V call() throws Exception; }
    public interface ActionListener { void actionPerformed(ActionEvent e); }
    public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
Syntax notes
The interface Comparator is functional because although it declares two abstract methods, one of these—equals— has a signature corresponding to a public method in Object. Interfaces always declare abstract methods corresponding to the public methods of Object, but they usually do so implicitly. Whether implicitly or explicitly declared, such methods are excluded from the count.
[Skip this note on a first reading.] The situation is complicated by the possibility that two interfaces might have methods that are not identical but are related by erasure. For example, the methods of the two interfaces
    interface Foo1 { void bar(List<String> arg); }
    interface Foo2 { void bar(List arg); }
are said to be override-equivalent. If the (functional) superinterfaces of an interface contain override-equivalent methods, the function type of that interface is defined as a method that can legally override all the inherited abstract methods. In this example, if

    interface Foo extends Foo1, Foo2 {}
then the function type of Foo is

    void bar(List arg);
In fact, every functional interface has such a function type, though in the more common and simpler case it is just the single abstract method of that interface.


6. Why can’t default methods override equals, hashCode, and toString?
An interface cannot provide a default implementation for any of the methods of the Object class. This is a consequence of the “class wins” rule for method resolution: a method found on the superclass chain always takes precedence over any default methods that appear in any superinterface. In particular, this means one cannot provide a default implementation for equals, hashCode, or toString from within an interface.

This seems odd at first, given that some interfaces actually define their equals behavior in documentation. The List interface is an example. So, why not allow this?

One reason is that it would become more difficult to reason about when a default method is invoked. The current rules are simple: if a class implements a method, that always wins over a default implementation. Since all instances of interfaces are subclasses of Object, all instances of interfaces have non-default implementations of equals, hashCode, and toString already. Therefore, a default version of these on an interface is always useless, and it may as well not compile.

Another reason is that providing default implementations of these methods in an interface is most likely misguided. These methods perform computations over the object’s state, but the interface, in general, has no access to state; only the implementing class has access to this state. Therefore, the class itself should provide the implementations, and default methods are unlikely to be useful.

For further reading, see this explanation written by Brian Goetz in response to “Allow default methods to override Object’s methods” on the lambda-dev mailing list in March, 2013.


7. Conflict resolutions in Functional Interfaces

https://www.javabrahman.com/java-8/java-8-multiple-inheritance-conflict-resolution-rules-and-diamond-problem/

8. https://www.javabrahman.com/category/java-8/

No comments:

Post a Comment