具有非SAM接口的lambdas的Java习惯用法

在Java中,使用lambda而不是匿名类可以优雅地实现具有单个抽象方法(即SAM类型或function接口)的接口:

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

可以替换为:

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

但是对于具有多个抽象方法的接口,无法直接应用lambda。 例如, java.awt.event.WindowListener有七种方法。 但通常一块代码只对定义这七种方法中的一种感兴趣。

要使用匿名类覆盖来实现行为,我们可以:

  // non-SAM with adapter implementation with override window.addWindowListener( new WindowAdapter() { @Override public void windowOpened(Event e){ System.out.println("WindowAdapter opened via override!"); } } ); 

但是有一个更优雅的方式与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!"); } } 

注意 :@ maythesource.com提出了一个类似但更广泛的问题:“ 如果想要在匿名类中实现多个方法,那么有人会使用MouseListener做什么? ”最受欢迎和接受的答案是使用匿名实现。 我的问题是关于非SAM类型的优雅lambda解决方案。 因此, 这个问题不是 Java 8 Lambda表达式 的重复 – 如何在嵌套类中使用多个方法 。


在Brian Goetz对另一个问题的回答中 ,他建议使用静态工厂方法。 在这种情况下,它有点单调乏味,因为WindowListener定义了七种处理程序方法,因此您需要定义七种静态工厂方法。 但这并不是那么糟糕,因为已经存在一个WindowAdapter类,它提供了所有方法的空实现。 (如果没有,你必须定义自己的等价物。)这是我如何做到的:

 class WLFactory { public static WindowListener windowOpened(Consumer c) { return new WindowAdapter() { @Override public void windowOpened(WindowEvent e) { c.accept(e); } }; } public static WindowListener windowClosing(Consumer c) { return new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { c.accept(e); } }; } // ... } 

(其他253个案例类似。)

每个工厂方法都创建一个WindowAdapter的子类,它覆盖适当的方法来调用传入的lambda表达式。不需要额外的适配器或桥接类。

它将使用如下:

 window.addWindowListener(WLFactory.windowOpened(we -> System.out.println("opened"))); 

我找到的最优雅的方法是使用匿名桥:

  // SAM bridge with lambda implementation window.addWindowListener( WindowBridge.windowOpened( b -> System.out.println("opening via lambda!") ) ); 

与SAM类型的情况一样,它比匿名适配器更干净:

  // non-SAM with adapter implementation with override window.addWindowListener( new WindowAdapter() { @Override public void windowOpened(Event e){ System.out.println("WindowAdapter opened via override!"); } } ); 

但它确实需要一个与静态工厂有点尴尬的桥梁:

 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 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 c) { return new WindowClosing() { public void windowClosing(Event e){ c.accept(e); } }; } } 

我想为此提出一个相当通用的解决方案:可以使用动态代理类来生成接口的实现。 这样的代理可以简单地忽略所有方法,除了将适当的Consumer指定为lambda的方法。

当然,必须小心使用reflection。 但优点是它可以与任何 MAM接口类型(多重抽象方法)一起“开箱即用”。

没有必要为所有接口及其方法创建数十个或数百个桥接方法。 只需创建一个代理,它是接口的“空”实现,并将单个方法实现作为lambda传递。

这里有一个基本的示例实现,表明它可以简洁而通用地用于不同的接口,如WindowListenerMouseListenerComponentListener

 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 create(Class c, String methodName, Consumer consumer) { Function function = new Function() { @Override public Object apply(Object[] t) { consumer.accept(t); return null; } }; return createFromFunction(c, methodName, function); } @SuppressWarnings("unchecked") private static  T createFromFunction(Class c, String methodName, Function 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 function; public LambdaDelegator(String methodName, Function 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()); } }