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.


0

4 Answers 4

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"))); 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, +Stuart Marks! It looks like we agree on cleanest lambda solution, as well as the override solution.
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); } }; } } 

6 Comments

Only slightly cleaner.
I just noticed that @Brian Goetz answered this question in stackoverflow.com/a/23373921/3342253 with his "officially recommended pattern".
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.
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.
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.
|
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()); } } 

3 Comments

It’s worth noting that such a solution already exists in Java itself since jdk1.4…
EventHandler.create(listenerType, consumer, "accept", "", listenerMethodName)
@Holger OK. This discussion can be cleaned up if you post this as an answer.
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 }); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.