Implementing multiple interfaces, Listener and Java Closures with lambda expressions


Implementing multiple interfaces

Sometimes you may want to have a lambda expression implementing more than one interface. This is mostly useful with marker interfaces (such as java.io.Serializable) since they don't add abstract methods.

For example, you want to create a TreeSet with a custom Comparator and then serialize it and send it over the network. The trivial approach:

TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));

doesn't work since the lambda for the comparator does not implement Serializable. You can fix this by using intersection types and explicitly specifying that this lambda needs to be serializable:

TreeSet<Long> ts = new TreeSet<>((Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));

If you're frequently using intersection types (for example, if you're using a framework such as Apache Spark where almost everything has to be serializable), you can create empty interfaces and use them in your code instead:

public interface SerializableComparator extends Comparator<Long>, Serializable {}
 
public class CustomTreeSet {
     public CustomTreeSet(SerializableComparator comparator) {}
}

This way you're guaranteed that the passed comparator will be serializable.


Lambda - Listener Example

Anonymous class listener

Before Java 8, it’s very common that an anonymous class is used to handle click event of a JButton, as shown in the following code. This example shows how to implement an anonymous listener within the scope of btn.addActionListener.

JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.println("Button was pressed");
	}
});

Lambda listener

Because the ActionListener interface defines only one method actionPerformed(), it is a functional interface which means there’s a place to use Lambda expressions to replace the boilerplate code. The above example can be re-written using Lambda expressions as follows:

JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
     System.out.println("Button was pressed");
});

Java Closures with lambda expressions

Anonymous class listener

A lambda closure is created when a lambda expression references the variables of an enclosing scope (global or local). The rules for doing this are the same as those for inline methods and anonymous classes.

Local variables from an enclosing scope that are used within a lambda have to be final. With Java 8 (the earliest version that supports lambdas), they don't need to be declared final in the outside context, but must be treated that way. For example:

int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};

This is legal as long as the value of the n variable is not changed. If you try to change the variable, inside or outside the lambda, you will get the following compilation error:

  • "local variables referenced from a lambda expression must be final or effectively final".

For example:

int n = 0;
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};
n++; // Will generate an error.

If it is necessary to use a changing variable within a lambda, the normal approach is to declare a final copy of the variable and use the copy. For example

int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++; // Now will not generate an error
r.run(); // Will run with i = 0 because k was 0 when the lambda was created

Naturally, the body of the lambda does not see the changes to the original variable.

Note that Java does not support true closures. A Java lambda cannot be created in a way that allows it to see changes in the environment in which it was instantiated. If you want to implement a closure that observes or makes changes to its environment, you should simulate it using a regular class. For example:

// Does not compile ...
public IntUnaryOperator createAccumulator() {
     int value = 0;
     IntUnaryOperator accumulate = (x) -> { value += x; return value; };
     return accumulate;
}

The above example will not compile for reasons discussed previously. We can work around the compilation error as follows:

// Compiles, but is incorrect ...
public class AccumulatorGenerator {
	private int value = 0;
	public IntUnaryOperator createAccumulator() {
		IntUnaryOperator accumulate = (x) -> { value += x; return value; };
		return accumulate;
	}
}

The problem is that this breaks the design contract for the IntUnaryOperator interface which states that instances should be functional and stateless. If such a closure is passed to built-in functions that accept functional objects, it is liable to cause crashes or erroneous behavior. Closures that encapsulate mutable state should be implemented as regular classes. For example.

// Correct ...
public class Accumulator {
	private int value = 0;
	public int accumulate(int x) {
		value += x;
		return value;
	}
}

Basic Programs