Java idiom for lambdas with non-SAM interfaces
The most elegant way I have found is to use an anonymous bridge:
// SAM bridge with lambda implementation
window.addWindowListener(
WindowBridge.windowOpened(
b -> System.out.println("opening via lambda!")
)
);
which, like the SAM type scenario, is cleaner than the anonymous adapter:
// non-SAM with adapter implementation with override
window.addWindowListener(
new WindowAdapter() {
@Override
public void windowOpened(Event e){
System.out.println("WindowAdapter opened via override!");
}
}
);
but it does require a slightly awkward bridge with a static factory:
import java.util.function.Consumer;
public interface WindowBridge {
// SAM for this method
public abstract class WindowOpened extends WindowAdapter {
public abstract void windowOpened(Event e);
}
// factory bridge
public static WindowOpened windowOpened(Consumer<Event> c) {
return new WindowOpened() {
public void windowOpened(Event e){
c.accept(e);
}
};
}
// SAM for this method
public abstract class WindowClosing extends WindowAdapter {
public abstract void windowClosing(Event e);
}
// factory bridge
public static WindowClosing windowClosing(Consumer<Event> c) {
return new WindowClosing() {
public void windowClosing(Event e){
c.accept(e);
}
};
}
}
In Brian Goetz' answer to the other question, he suggested using static factory methods. In this case it's a bit tedious, since WindowListener
defines seven handler methods, so you'd need to define seven static factory methods. This isn't that bad, though, since there is already a WindowAdapter
class that provides empty implementations of all of the methods. (If there isn't one, you'd have to define your own equivalent.) Here's how I'd do it:
class WLFactory {
public static WindowListener windowOpened(Consumer<WindowEvent> c) {
return new WindowAdapter() {
@Override public void windowOpened(WindowEvent e) { c.accept(e); }
};
}
public static WindowListener windowClosing(Consumer<WindowEvent> c) {
return new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { c.accept(e); }
};
}
// ...
}
(The other 253 cases are analogous.)
Each factory method creates a subclass of WindowAdapter
that overrides the appropriate method to call the lambda expression that's passed in. No need for additional adapter or bridge classes.
It would be used as follows:
window.addWindowListener(WLFactory.windowOpened(we -> System.out.println("opened")));
I'd like to propose a rather generic solution for this: One can use Dynamic Proxy Classes to generate the implementation of the interface. Such a proxy could simply ignore all methods, except for the method for which an appropriate Consumer
was specified as a lambda.
Of course, reflection always has to be used with care. But the advantage is that it works "out of the box" with any MAM-interface-type (Multiple Abstract Method).
There's no need to create dozens or hundreds of bridge methods for all the interfaces and their methods. Just create a proxy that is an "empty" implementation of the interface, and pass in a single method implementation as a lambda.
A basic example implementation is here, showing that it may be used concisely and generically for different interfaces, like WindowListener
, MouseListener
and ComponentListener
:
import java.awt.event.ComponentListener;
import java.awt.event.MouseListener;
import java.awt.event.WindowListener;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.function.Consumer;
import java.util.function.Function;
class LambdaDelegatorTest
{
public static void main(String args[])
{
WindowListener w =
LambdaDelegators.create(WindowListener.class, "windowClosed",
e -> System.out.println("Window closed"));
w.windowActivated(null);
w.windowClosed(null);
MouseListener m =
LambdaDelegators.create(MouseListener.class, "mouseExited",
e -> System.out.println("Mouse exited"));
m.mouseClicked(null);
m.mouseExited(null);
ComponentListener c =
LambdaDelegators.create(ComponentListener.class, "componentShown",
e -> System.out.println("Component shown"));
c.componentHidden(null);
c.componentShown(null);
}
}
class LambdaDelegators
{
public static <T> T create(Class<T> c, String methodName,
Consumer<Object[]> consumer)
{
Function<Object[], Object> function = new Function<Object[], Object>()
{
@Override
public Object apply(Object[] t)
{
consumer.accept(t);
return null;
}
};
return createFromFunction(c, methodName, function);
}
@SuppressWarnings("unchecked")
private static <T> T createFromFunction(Class<T> c, String methodName,
Function<Object[], Object> function)
{
Class<?> classes[] = new Class[1];
classes[0] = c;
Object proxy =
Proxy.newProxyInstance(c.getClassLoader(), classes,
new LambdaDelegator(methodName, function));
return (T) proxy;
}
private LambdaDelegators()
{
}
}
class LambdaDelegator implements InvocationHandler
{
private static final Method hashCodeMethod;
private static final Method equalsMethod;
private static final Method toStringMethod;
static
{
try
{
hashCodeMethod = Object.class.getMethod(
"hashCode", (Class<?>[]) null);
equalsMethod = Object.class.getMethod(
"equals", new Class[] { Object.class });
toStringMethod = Object.class.getMethod(
"toString", (Class<?>[]) null);
}
catch (NoSuchMethodException e)
{
throw new NoSuchMethodError(e.getMessage());
}
}
private final String methodName;
private final Function<Object[], Object> function;
public LambdaDelegator(String methodName,
Function<Object[], Object> function)
{
this.methodName = methodName;
this.function = function;
}
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable
{
Class<?> declaringClass = m.getDeclaringClass();
if (declaringClass == Object.class)
{
if (m.equals(hashCodeMethod))
{
return proxyHashCode(proxy);
}
else if (m.equals(equalsMethod))
{
return proxyEquals(proxy, args[0]);
}
else if (m.equals(toStringMethod))
{
return proxyToString(proxy);
}
else
{
throw new InternalError(
"unexpected Object method dispatched: " + m);
}
}
else
{
if (m.getName().equals(methodName))
{
return function.apply(args);
}
}
return null;
}
private Integer proxyHashCode(Object proxy)
{
return new Integer(System.identityHashCode(proxy));
}
private Boolean proxyEquals(Object proxy, Object other)
{
return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
}
private String proxyToString(Object proxy)
{
return proxy.getClass().getName() + '@' +
Integer.toHexString(proxy.hashCode());
}
}