Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.

Java 8 Revealed: Lambdas, Default Methods and Bulk Data Operations

Capturing variables

If a lambda expression accesses a non-static variable or an object that is defined outside of lambda body, then we have a situation when the lambda expression captures the outer scope, i.e. it is a capturing lambda.

Consider the comparator example:

int minus_one = -1;
int one = 1;
int zero = 0;
Comparator cmp = (x, y) -> (x < y) ? minus_one : ((x > y) ? one : zero);

In order for this lambda expression to be valid, the variables minus_one, one and zero, it captures must be “effectively final”. It means that the variables should either be declared final, or they should not be re-assigned.

Lambdas as return values

In the examples above, the interfaces were used as parameters of some other method. However, the use of the functional interface is not constrained to a parameter but it also can be a return type of a method. It means that we can actually return a lambda from a method:

public class ComparatorFactory {
  public Comparator makeComparator(){
    return Integer::compareUnsigned;
  }
}

The example above demonstrates a valid code of a method that returns a method reference. In fact, method reference cannot be returned from a method just like that. Instead, the compiler will generate code, using the invokedynamic bytecode instruction, to evaluate it to a method call that returns an instance of a Comparator interface. So the client code will just assume it works with an interface:

Comparator cmp =
    new ComparatorFactory().makeComparator();
    cmp.compare(10, -5); // -1

Serializing lambdas

The code snippet used in the previous section, produces a Comparator instance that could be used by the client code. It all works beautifully. However, there’s one serious drawback – if we try to serialize the comparator instance the code will throw NotSerializableException.

By default, lambdas could not be made serializable as it would be a security threat. To fix this issue, the so-called type intersection was introduced to Java 8:

public class ComparatorFactory {
  public Comparator makeComparator() {
    return (Comparator & Serializable) Integer::compareUnsigned;
  }
}

Serializable interface is generally know as a marker interface – it does not declare any methods. It can also be referred as ZAM (Zero Abstract Methods) type.


The general rule of application for the type intersection is as follows:

SAM & ZAM1 & ZAM2 & ZAM3

 

It means that if the result is of SAM type, then we can “intersect” it with one or more ZAM types. Effectively, we now say that the resulting instance of the Comparator interface is now also Serializable.

By casting the result as shown above, the compiler generates one more method into the compiled class:

private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);

Again, by applying the invokedynamic bytecode instruction trickery, the compiler will bind the invocation of $deserializeLambda$(..) method when an instance of the Comparator is created by makeComparator() method.

 

Download the PDF