11

In Java, interfaces with a single abstract method (i.e., SAM types or functional interfaces) can be elegantly implemented with lambda instead of an anonymous class:

    // SAM ActionListener with anonymous implementation
    button.addActionListener(
        new ActionListener(){
            public void actionPerformed(Event e){
                System.out.println("button via anon!");
            }
        }
    );

can be replaced with:

    // SAM ActionListener with lambda implementation
    button.addActionListener(
        e -> System.out.println("button via lambda!")
    );

But for interfaces with multiple abstract methods, lambda cannot be directly applied. For example, java.awt.event.WindowListener has seven methods. But often a chunk a code is only interested in defining one of these seven methods.

To implement the behavior with an anonymous class override, we can:

    // 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 is there a more elegant way with lambdas?

@FunctionalInterface
public interface ActionListener {
    void actionPerformed(Event e);
}

public interface WindowListener {
    void windowOpened(Event e);

    void windowClosing(Event e);
}

public class WindowAdapter implements WindowListener {

    public void windowOpened(Event e){
        System.out.println("windowOpened in adapter!");
    }

    public void windowClosing(Event e){
        System.out.println("windowClosing in adapter!");
    }
}

Note: @maythesource.com asked a similar, but broader question: "What would someone do with a MouseListener if they wanted to implement multiple methods within the anonymous class?" The most upvoted and accepted answer is to use an anonymous implementation. My question is about an elegant lambda solution for non-SAM types. Therefore, this question is not a duplicate of Java 8 Lambda Expressions - what about multiple methods in nested class.


Community
  • 1
  • 1
Jan Nielsen
  • 10,892
  • 14
  • 65
  • 119

4 Answers4

11

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")));
Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
4

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);
            }
        };
    }
}
Jan Nielsen
  • 10,892
  • 14
  • 65
  • 119
  • 2
    Only slightly cleaner. – user253751 Aug 14 '14 at 04:27
  • I just noticed that @Brian Goetz answered this question in http://stackoverflow.com/a/23373921/3342253 with his "officially recommended pattern". – Jan Nielsen Aug 14 '14 at 04:32
  • Yeah, agreed @immibis - the additional static bridge method, while hidden, is still a little tricky. The application code is cleaner like in the SAM scenario, though. – Jan Nielsen Aug 14 '14 at 04:38
  • 4
    actually I find this method very nice and readable. The bridge is indeed a bit awkward, but on the calling side it is immediately visible what's happening. – Joeri Hendrickx Aug 14 '14 at 10:47
  • 4
    What’s strange about this solution is the creation of these obsolete types `WindowOpened` and `WindowClosing` whereas the solution would also work without them, i.e. creating anonymous subclasses of `WindowAdapter` directly. It’s also unclear why `WindowBridge` is an `interface` as it is not meant to be implemented by anyone. – Holger Dec 19 '14 at 15:14
  • Not sure for what you need the 'WindowBridge'. I find the solution of @Holger from [here](http://stackoverflow.com/a/21851111/1465758) easier ... – Steffen Jul 28 '15 at 15:20
1

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());
    }
}
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • 3
    It’s worth noting that such a solution [already exists](http://docs.oracle.com/javase/7/docs/api/java/beans/EventHandler.html) in Java itself since jdk1.4… – Holger May 18 '15 at 11:19
  • 1
    `EventHandler.create(listenerType, consumer, "accept", "", listenerMethodName)` – Holger May 18 '15 at 13:13
  • @Holger OK. This discussion can be cleaned up if you post this as an answer. – Marco13 May 18 '15 at 13:19
0

From Java 8 you can provide default implementation for interface methods. Create a new interface that extends WindowListener with all but one methods implemented:

@FunctionalInterface
public interface WindowOpenedListener extends WindowListener {
    @Override
    default void windowClosing(WindowEvent e) {}

    @Override
    default void windowClosed(WindowEvent e) {}

    @Override
    default void windowIconified(WindowEvent e) {}

    @Override
    default void windowDeiconified(WindowEvent e) {}

    @Override
    default void windowActivated(WindowEvent e) {}

    @Override
    default void windowDeactivated(WindowEvent e) {}
}

as you can see windowOpened is the only method that is left unimplemented by the interface, so you can:

window.addWindowListener((WindowOpenedListener) e -> {
    // windowsOpened implementation
});

One can generalize multiple methods (if it make sense), to do so implement all methods of WindowListener and add a new abstract one, like:

@FunctionalInterface
public interface WindowStatusListener extends WindowListener {
    void statusChanged(WindowEvent e);

    @Override
    default void windowOpened(WindowEvent e) { statusChanged(e); }

    @Override
    default void windowClosing(WindowEvent e) {}

    @Override
    default void windowClosed(WindowEvent e) { statusChanged(e); }

    @Override
    default void windowIconified(WindowEvent e) {}

    @Override
    default void windowDeiconified(WindowEvent e) {}

    @Override
    default void windowActivated(WindowEvent e) {}

    @Override
    default void windowDeactivated(WindowEvent e) {}
}

and then

window.addWindowListener((WindowStatusListener) e -> {
    // statusChanged implementation
});
Andrey Megvinov
  • 421
  • 5
  • 4