Указатели на функции в Java
Это может быть что-то обычное и тривиальное, но мне кажется, что мне трудно найти конкретный ответ. В C# существует концепция делегатов, которая тесно связана с идеей указателей на функции из C++. Есть ли подобная функциональность в Java? Учитывая, что указатели в некоторой степени отсутствуют, как лучше всего об этом? И чтобы быть ясным, мы говорим здесь первым классом.
12 ответов
Идиома 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);
Вы можете заменить указатель на функцию интерфейсом. Допустим, вы хотите просмотреть коллекцию и что-то сделать с каждым элементом.
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++;
}
});
Вы можете использовать отражение, чтобы сделать это.
Передайте в качестве параметра объект и имя метода (в виде строки), а затем вызовите метод. Например:
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");
Конечно, проверьте все исключения и добавьте необходимые приведения.
Нет, функции не являются объектами первого класса в Java. Вы можете сделать то же самое, реализовав класс-обработчик - так реализованы обратные вызовы в Swing и т. Д.
Однако в будущих версиях java есть предложения по закрытию (официальное название того, о чем вы говорите) - у Javaworld есть интересная статья.
Это напоминает Казнь Стива Йегге в Королевстве Существительных. В основном это говорит о том, что Java нуждается в объекте для каждого действия, и, следовательно, не имеет "глагольных" сущностей, таких как указатели функций.
Для достижения аналогичной функциональности вы можете использовать анонимные внутренние классы.
Если бы вы должны были определить интерфейс 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.
}
}
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
}
}
В 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");
}
}
}
Я реализовал поддержку обратного вызова / делегата в Java, используя отражение. Подробности и рабочий источник доступны на моем сайте.
Как это устроено
У нас есть основной класс с именем Callback с вложенным классом с именем WithParms. API, которому требуется обратный вызов, примет объект Callback в качестве параметра и, если необходимо, создаст Callback.WithParms в качестве переменной метода. Поскольку многие из приложений этого объекта будут рекурсивными, это работает очень чисто.
Поскольку производительность по-прежнему остается для меня высоким приоритетом, я не хотел, чтобы мне требовалось создавать одноразовый массив объектов для хранения параметров для каждого вызова - в конце концов, в большой структуре данных могут быть тысячи элементов и обработка сообщений В этом сценарии мы могли бы обработать тысячи структур данных в секунду.
Чтобы быть потокобезопасным, массив параметров должен существовать уникально для каждого вызова метода API, и для эффективности тот же самый должен использоваться для каждого вызова обратного вызова; Мне нужен был второй объект, который было бы дешево создать, чтобы связать обратный вызов с массивом параметров для вызова. Но в некоторых сценариях вызывающий уже имеет массив параметров по другим причинам. По этим двум причинам массив параметров не принадлежит объекту Callback. Кроме того, выбор вызова (передача параметров в виде массива или отдельных объектов) принадлежит API, использующему обратный вызов, позволяющий ему использовать любой вызов, который лучше всего подходит для его внутренней работы.
Тогда вложенный класс WithParms является необязательным и служит двум целям: он содержит массив объектов параметров, необходимый для вызовов обратного вызова, и предоставляет 10 перегруженных методов invoke() (с 1 до 10 параметров), которые загружают массив параметров, а затем вызвать цель обратного вызова.
Проверьте замыкания, как они были реализованы в библиотеке lambdaj. Они на самом деле ведут себя очень похоже на делегаты C#:
Использование интерфейсов для отправки функций в качестве параметров можно рассматривать только как обходной путь, поскольку любой другой язык будет использовать указатель/ссылку на функцию.
Ваш вопрос не совсем ясен, но вот как вы можете точно отправить ссылку на функцию в Java:
import java.util.Arrays;
import java.util.function.Function;
public class FunctionPointerDemo2 {
static int increment(int n) {
return n + 1;
}
static void myObjective(int[] arr, Function<Integer, Integer> f) {
for (int i = 0; i < arr.length; ++i) {
arr[i] = f.apply(arr[i]);
}
}
public static void main(String[] args) {
int[] arr = {0, 1, 2, 3, 4};
myObjective(arr, FunctionPointerDemo2::increment);
System.out.println(Arrays.toString(arr));
}
}
По отношению к большинству людей здесь я новичок в 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();