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

时间:2014-08-14 03:37:58

标签: java lambda java-8

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

    // 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 Expressions - what about multiple methods in nested class的重复


4 个答案:

答案 0 :(得分:7)

Brian Goetz' answer to the other question中,他建议使用静态工厂方法。在这种情况下,由于WindowListener定义了七个处理程序方法,所以它有点单调乏味,因此您需要定义七个静态工厂方法。但是,这并不是很糟糕,因为已经有一个WindowAdapter类提供了所有方法的空实现。 (如果没有,你必须定义自己的等价物。)以下是我的表现:

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.)

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

将使用如下:

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

答案 1 :(得分:4)

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

    // 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<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);
            }
        };
    }
}

答案 2 :(得分:1)

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

当然,必须小心使用反射。但它的优点是它可以开箱即用&#34;开箱即用&#34;使用任何 MAM接口类型(多个抽象方法)。

不需要为所有接口及其方法创建数十个或数百个桥接方法。只需创建一个&#34;空的&#34;接口的实现,并将单个方法实现作为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> 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 :(得分:0)

在Java 8中,您可以提供接口方法的默认实现。创建一个扩展WindowListener的新接口,该接口除了已实现的所有方法外:

@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) {}
}

如您所见,windowOpened是界面上唯一未实现的方法,因此您可以:

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

可以推广多种方法(如果有意义),以实现WindowListener的所有方法并添加新的抽象方法,例如:

@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) {}
}

然后

window.addWindowListener((WindowStatusListener) e -> {
    // statusChanged implementation
});