Идиома Java для lambda с интерфейсами, отличными от SAM

В Java интерфейсы с одним абстрактным методом (т. Е. Типы SAM или функциональные интерфейсы) могут быть элегантно реализованы с помощью lambda вместо анонимного classа:

// 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 имеет семь методов. Но часто кусок кода интересует только один из этих семи методов.

Чтобы реализовать поведение с переопределением анонимного classа, мы можем:

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

но есть ли более элегантный способ с lambdaми?

 @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, если бы они хотели реализовать несколько методов в анонимном classе? » Самый распространенный и принятый ответ – использовать анонимную реализацию. Мой вопрос касается элегантного lambda-решения для не-SAM-типов. Таким образом, этот вопрос не является дублированием Java 8 Lambda Expressions – о нескольких методах в вложенном classе .


В ответ Брайан Гетц на другой вопрос он предложил использовать статические заводские методы. В этом случае это немного утомительно, так как WindowListener определяет семь методов обработчика, поэтому вам нужно определить семь статических заводских методов. Однако это не так уж плохо, поскольку уже существует class 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 случая аналогичны.)

Каждый заводский метод создает подclass WindowAdapter который переопределяет соответствующий метод для вызова передаваемого lambda-выражения. Нет необходимости в дополнительных classах адаптера или моста.

Он будет использоваться следующим образом:

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

Я хотел бы предложить довольно общее решение для этого: можно использовать динамические прокси-classы для генерации реализации интерфейса. Такой прокси мог просто игнорировать все методы, за исключением метода, для которого соответствующий Consumer был указан как lambda.

Разумеется, рефлексию всегда нужно использовать с осторожностью. Но преимущество заключается в том, что он работает «из коробки» с любым типом MAM-интерфейса (Multiple Abstract Method).

Нет необходимости создавать десятки или сотни мостовых методов для всех интерфейсов и их методов. Просто создайте прокси-сервер, который является «пустой» реализацией интерфейса, и передайте в единую реализацию метода как lambda.

Ниже приведен пример реализации базового примера, показывающий, что он может использоваться кратко и в целом для разных интерфейсов, таких как MouseListener , MouseListener и 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 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()); } } 
  • Лямбда эта ссылка в java
  • Как отличить выражение <Func к выражению <Func >
  • java lambda возвращает лямбду
  • Имеют ли переменные захвата lambdas c ++ 11, которые они не используют?
  • Как динамически создать предикат Expression <Func >?
  • Является ли lambda-выражение созданием объекта в куче каждый раз, когда оно выполняется?
  • Поддерживается ли constexpr с lambda-функциями / выражениями?
  • Как удалить обработчик события lambda
  • Функция Java 8 Lambda, которая генерирует исключение?
  • Java Lambda Stream Distinct () на произвольном ключе?
  • Что такое функциональный интерфейс в Java 8?
  • Interesting Posts

    Вставить несколько столбцов вместе

    Может ли HTML5 взаимодействовать с периферийными устройствами, такими как сканеры и считыватели кредитных карт?

    javax.net.ssl.SSLPeerUnverifiedException: имя хоста не соответствует теме сертификата, предоставленной партнером

    Объявление функции: K & R vs ANSI

    Как напечатать тип int64_t в C

    Ошибка: конфликт с зависимостью «com.google.code.findbugs: jsr305»

    Почему Golang обрабатывает закрытие по-разному в goroutines?

    ADO.Net Entity Framework Объект сущности не может ссылаться на несколько экземпляров IEntityChangeTracker

    Каковы некоторые советы по сокращению дискового пространства, используемого Windows 7?

    jQuery detect click on disabled submit button

    .NET – WindowStyle = hidden vs. CreateNoWindow = true?

    Что такое Log API для вызова программы Android JNI?

    Программировать блокировку в портретном режиме для определенных операций

    SQL-запрос возвращает данные из нескольких таблиц

    Как изменить загрузочную анимацию Windows 7?

    Давайте будем гением компьютера.