Указатели на функции в Java

148

Это может быть что-то обычное и тривиальное, но мне кажется, что мне трудно найти конкретный ответ. В C # существует концепция делегатов, которая тесно связана с идеей указателей на функции из C ++. Есть ли подобная функциональность в Java? Учитывая, что указатели в некоторой степени отсутствуют, что лучше для этого? И чтобы быть ясным, мы говорим здесь первым классом.

dreadwail
источник
1
Мне просто любопытно, почему вы хотели бы, чтобы слушатели или другие конструкции ООП выполняли ту же задачу, сохраняя при этом свою объектную природу. Я имею в виду, что я понимаю необходимость функциональности, предлагаемой концепцией, просто для того, чтобы ее можно было достичь с помощью простого объекта ... если, конечно, я что-то упустил, отсюда и мой вопрос! :-)
Newtopian
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
Да, я на самом деле возьму указатели на C-функции над грубыми классами-обертками в любой день
BT
3
Java 8 имеет лучший синтаксис для этого.
Турбьерн Равн Андерсен
65

Вы можете заменить указатель на функцию интерфейсом. Допустим, вы хотите просмотреть коллекцию и что-то сделать с каждым элементом.

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

Это интерфейс, который мы могли бы передать, скажем, CollectionUtils2.doFunc (Коллекция 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");

Конечно, проверьте все исключения и добавьте необходимые приведения.

zoquete
источник
7
Отражение не является правильным ответом ни на что, кроме «Как мне написать медленный хитрый Java-код»: D
Джейкоб
5
Это может быть "некрасиво" и медленно, но я считаю, что рефлексия позволяет делать вещи, которые не может сделать решение на основе интерфейса в принятом ответе (если я не ошибаюсь), а именно вызывать различные методы одного и того же объекта в одном и том же коде часть.
Евсевий
@zoquete .. Я просматривал этот пост и сомневался ... так что в вашем подходе, если я получу доступ к переменной "theDescription", функция будет вызываться каждый раз, когда к переменной обращаются ??
karthik27
20

Нет, функции не являются объектами первого класса в Java. Вы можете сделать то же самое, реализовав класс-обработчик - так реализованы обратные вызовы в Swing и т. Д.

Однако в будущих версиях java есть предложения по закрытию (официальное название того, о чем вы говорите) - у Javaworld есть интересная статья.

jwoolard
источник
15

Это напоминает Казнь Стива Йегге в Королевстве Существительных . В основном это говорит о том, что Java нуждается в объекте для каждого действия, и поэтому не имеет «только глагольных» сущностей, таких как указатели на функции.

Юваль Ф
источник
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.
    }
}
Kingamajick
источник
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
   }
}
Alex
источник
Есть ли понятие alias: например, public List<T> collect(T t) = Collections::collect
Джавадба
@javadba, насколько я знаю, нет.
Алекс
5

В Java такого нет. Вам нужно будет обернуть вашу функцию в некоторый объект и передать ссылку на этот объект, чтобы передать ссылку на метод этого объекта.

Синтаксически это может быть в определенной степени облегчено с помощью анонимных классов, определенных на месте, или анонимных классов, определенных как переменные-члены класса.

Пример:

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");
        }
    }
}
VoidPointer
источник
3

Я реализовал поддержку обратного вызова / делегата в Java, используя отражение. Подробности и рабочий источник доступны на моем сайте .

Как это устроено

У нас есть основной класс с именем Callback с вложенным классом с именем WithParms. API, которому требуется обратный вызов, примет объект Callback в качестве параметра и, если необходимо, создаст Callback.WithParms в качестве переменной метода. Поскольку многие из приложений этого объекта будут рекурсивными, это работает очень чисто.

Поскольку производительность по-прежнему остается для меня высоким приоритетом, я не хотел, чтобы мне требовалось создавать одноразовый массив объектов для хранения параметров для каждого вызова - в конце концов, в большой структуре данных могут быть тысячи элементов и обработка сообщений В этом сценарии мы можем обработать тысячи структур данных в секунду.

Чтобы быть потокобезопасным, массив параметров должен существовать уникально для каждого вызова метода API, и для эффективности тот же самый должен использоваться для каждого вызова обратного вызова; Мне нужен был второй объект, который было бы дешево создать, чтобы связать обратный вызов с массивом параметров для вызова. Но в некоторых сценариях вызывающий уже имеет массив параметров по другим причинам. По этим двум причинам массив параметров не принадлежит объекту Callback. Кроме того, выбор вызова (передача параметров в виде массива или отдельных объектов) принадлежит API, использующему обратный вызов, позволяющий использовать любой вызов, который лучше всего подходит для его внутренней работы.

Тогда вложенный класс WithParms является необязательным и служит двум целям: он содержит массив объектов параметров, необходимый для вызовов обратного вызова, и предоставляет 10 перегруженных методов invoke () (с числом параметров от 1 до 10), которые загружают массив параметров, а затем вызвать цель обратного вызова.

Лоуренс Дол
источник
2

Проверьте замыкания, как они были реализованы в библиотеке lambdaj. Они на самом деле ведут себя очень похоже на делегаты C #:

http://code.google.com/p/lambdaj/wiki/Closures

Марио Фуско
источник
-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();
ozgeneral
источник