Функціональні покажчики на Java


148

Це може бути щось загальне і тривіальне, але, здається, у мене виникають проблеми з пошуком конкретної відповіді. У C # існує концепція делегатів, яка сильно стосується ідеї функціональних покажчиків від C ++. Чи є подібний функціонал у Java? З огляду на те, що вказівники дещо відсутні, який найкращий спосіб для цього? І щоб було зрозуміло, ми говоримо тут про перший клас.


1
Мені просто цікаво, чому б ви хотіли цього, коли слухачі чи інша конструкція OOP зробили би те саме завдання, зберігаючи свою об'єктивну природу. Я маю на увазі, що я розумію необхідність функціоналу, пропонованого концепцією, лише в тому, що його можна досягти звичайним об'єктом. Якщо, звичайно, я чогось не пропускаю, отже, я задаю це питання! :-)
Ньютопський

2
У Java 8 є лямбда-вирази: docs.oracle.com/javase/tutorial/java/javaOO/… Ви можете перевірити це. Не зовсім функціональний покажчик, але все-таки може бути кориснішим.
FrustratedWithFormsDesigner

6
Посилання на метод Java 8 - саме те, про що ви просите.
Стівен

8
Посилання на метод Java 8 - саме те, про що ви просите. this::myMethodсемантично те саме, що створювати лямбда paramA, paramB -> this.myMethod(paramA, paramB).
Стівен

Відповіді:


126

Ідіома Java для функціонально-вказівної функції є анонімним класом, що реалізує інтерфейс, наприклад

Collections.sort(list, new Comparator<MyClass>(){
    public int compare(MyClass a, MyClass b)
    {
        // compare objects
    }
});

Оновлення: вищевикладене є необхідним у версіях Java до Java 8. Зараз у нас є набагато приємніші альтернативи, а саме лямбда:

list.sort((a, b) -> a.isGreaterThan(b));

посилання на методи:

list.sort(MyClass::isGreaterThan);

2
Шаблон дизайну стратегії. Настільки некрасиво, як покажчик функції C або C ++, коли ви хочете, щоб функція використовувала деякі не глобальні дані, які не надаються як аргумент функції.
Raedwald

75
@Raedwald C ++ має функтори, а C ++ 0x має заглушки та лямбдахи, побудовані поверх функторів. Він навіть має std::bind, що прив'язує параметри до функцій та повертає об'єкт, що викликається. Я не можу захистити C з цих причин, але C ++ дійсно є краще , ніж Java для цього.
zneak

21
@zneak, @Raedwald: Ви можете зробити це в C, перейшовши по вказівнику на дані користувача (наприклад: struct). (і може інкапсулювати його з кожним новим рівнем непрямості зворотного виклику) Це насправді досить чисто.
брич

25
Так, я дійсно прийматиму покажчики на функціональність С на більш жорсткі класи обгортки
BT

3
У Java 8 для цього кращий синтаксис.
Thorbjørn Ravn Andersen

65

Ви можете замінити покажчик функції інтерфейсом. Скажімо, ви хочете пробігти колекцію і зробити щось із кожним елементом.

public interface IFunction {
  public void execute(Object o);
}

Це інтерфейс, який ми могли б передати деяким мовлям CollectionUtils2.doFunc (Collection c, IFunction f).

public static void doFunc(Collection c, IFunction f) {
   for (Object o : c) {
      f.execute(o);
   }
}

Як приклад скажімо, у нас є колекція чисел, і ви хочете додати 1 до кожного елемента.

CollectionUtils2.doFunc(List numbers, new IFunction() {
    public void execute(Object o) {
       Integer anInt = (Integer) o;
       anInt++;
    }
});

42

Ви можете використовувати рефлексію для цього.

Передайте в якості параметра об'єкт і ім'я методу (як рядок), а потім викликайте метод. Наприклад:

Object methodCaller(Object theObject, String methodName) {
   return theObject.getClass().getMethod(methodName).invoke(theObject);
   // Catch the exceptions
}

А потім використовуйте його як у:

String theDescription = methodCaller(object1, "toString");
Class theClass = methodCaller(object2, "getClass");

Звичайно, перевірте всі винятки та додайте потрібні касти.


7
Рефлексія - це не правильна відповідь ні на що, окрім "Як писати повільний коверний код Java": D
Яків

5
Це може бути "потворно" і повільно, але я вважаю, що відображення дозволяє робити речі, які інтерфейсне рішення в прийнятій відповіді не може робити (якщо я не помиляюся), а саме викликати різні методи одного і того ж об'єкта в межах одного коду порція.
Євсевій

@zoquete .. Я проходив цю посаду і мав сумніви .. тож у вашому підході, якщо я отримаю доступ до змінної "TheDescription", функція буде викликатися кожен і кожен раз, коли доступ до змінної ??
karthik27

20

Ні, функції - не першокласні об'єкти в Java. Можна зробити те ж саме, застосувавши клас обробника - ось як реалізуються зворотні дзвінки в Swing і т.д.

Однак є пропозиції щодо закриття (офіційна назва того, про що ви говорите) у майбутніх версіях Java - у Javaworld є цікава стаття.



8

Для досягнення подібної функціональності ви можете використовувати анонімні внутрішні класи.

Якщо ви хочете визначити інтерфейс Foo:

interface Foo {
    Object myFunc(Object arg);
}

Створіть метод, barякий отримає "покажчик функції" як аргумент:

public void bar(Foo foo) {
    // .....
    Object object = foo.myFunc(argValue);
    // .....
}

Нарешті викличте метод наступним чином:

bar(new Foo() {
    public Object myFunc(Object arg) {
        // Function code.
    }
}

7

Java8 представила лямбда та посилання на методи . Тож якщо ваша функція відповідає функціональному інтерфейсу (ви можете створити свій власний), ви можете використовувати посилання на метод у цьому випадку.

Java надає набір загальних функціональних інтерфейсів . тоді як ви можете зробити наступне:

public class Test {
   public void test1(Integer i) {}
   public void test2(Integer i) {}
   public void consumer(Consumer<Integer> a) {
     a.accept(10);
   }
   public void provideConsumer() {
     consumer(this::test1);   // method reference
     consumer(x -> test2(x)); // lambda
   }
}

чи існує поняття alias: напр. public List<T> collect(T t) = Collections::collect
javadba

@javadba, наскільки я не знаю.
Олексій

5

На Яві такого немає. Вам потрібно загорнути свою функцію в якийсь об'єкт і передати посилання на цей об'єкт, щоб передати посилання на метод на цьому об'єкті.

Синтаксично це можна певною мірою полегшити, використовуючи анонімні класи, визначені на місці, або анонімні класи, визначені як членські змінні класу.

Приклад:

class MyComponent extends JPanel {
    private JButton button;
    public MyComponent() {
        button = new JButton("click me");
        button.addActionListener(buttonAction);
        add(button);
    }

    private ActionListener buttonAction = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // handle the event...
            // note how the handler instance can access 
            // members of the surrounding class
            button.setText("you clicked me");
        }
    }
}

3

Я реалізував підтримку зворотного дзвінка / делегата в Java за допомогою відображення. Деталі та робоче джерело доступні на моєму веб-сайті .

Як це працює

У нас є принцип класу з назвою Callback з вкладеним класом на ім'я WithParms. API, який потребує зворотного виклику, буде приймати об'єкт зворотного виклику як параметр і, якщо необхідно, створить Callback.WithParms як змінну методу. Оскільки велика кількість застосувань цього об’єкта буде рекурсивною, це працює дуже чисто.

Оскільки ефективність як і раніше залишається для мене високим пріоритетом, я не хотів вимагати, щоб я створював масив об'єктів, що викидає, щоб утримувати параметри для кожного виклику - адже у великій структурі даних може бути тисячі елементів і в обробці повідомлень сценарій ми могли б закінчити обробку тисяч структур даних в секунду.

Для того, щоб бути безпечним у потоці, масив параметрів повинен існувати унікально для кожного виклику методу API, а для ефективності той самий повинен використовуватися для кожного виклику зворотного виклику; Мені знадобився другий об'єкт, який було б дешево створити, щоб зв’язати зворотний виклик з масивом параметрів для виклику. Але, в деяких сценаріях, у виклику вже був би масив параметрів з інших причин. З цих двох причин масив параметрів не належав до об'єкта Callback. Також вибір виклику (передаючи параметри як масив або як окремі об'єкти) належить до рук API, використовуючи зворотний виклик, що дозволяє йому використовувати той виклик, який найкраще підходить для його внутрішньої роботи.

Потім вкладений клас WithParms є необов'язковим і виконує дві цілі, він містить масив об'єкта параметрів, необхідний для викликів зворотного виклику, і він пропонує 10 перевантажених методів виклику () (з 1 до 10 параметрів), які завантажують масив параметрів, а потім викликати ціль зворотного дзвінка.



-1

Відносно більшості людей тут я новачок у Java, але оскільки я не бачив подібної пропозиції, у мене є ще одна альтернатива. Я не впевнений, чи це хороша практика чи ні, або навіть пропонувався раніше, і я просто не зрозумів цього. Мені це подобається, оскільки я вважаю його самоописовим.

 /*Just to merge functions in a common name*/
 public class CustomFunction{ 
 public CustomFunction(){}
 }

 /*Actual functions*/
 public class Function1 extends CustomFunction{
 public Function1(){}
 public void execute(){...something here...}
 }

 public class Function2 extends CustomFunction{
 public Function2(){}
 public void execute(){...something here...}
 }

 .....
 /*in Main class*/
 CustomFunction functionpointer = null;

то залежно від програми призначте

 functionpointer = new Function1();
 functionpointer = new Function2();

тощо.

і дзвонити по

 functionpointer.execute();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.