Какая ближайшая замена для указателя функции в Java?

У меня есть метод, содержащий около десяти строк кода. Я хочу создать больше методов, которые будут делать то же самое, за исключением небольшого вычисления, которое изменит одну строку кода. Это идеальное приложение для передачи указателя функции для замены этой одной строки, но Java не имеет указателей на функции. Какая моя лучшая альтернатива?

Ответ 1

Анонимный внутренний класс

Предположим, что вы хотите передать функцию с параметром String, который возвращает int.
Сначала вам нужно определить интерфейс с функцией как своим единственным членом, если вы не можете повторно использовать существующую.

interface StringFunction {
    int func(String param);
}

Метод, который принимает указатель, просто примет экземпляр StringFunction так:

public void takingMethod(StringFunction sf) {
   int i = sf.func("my string");
   // do whatever ...
}

И будет вызвано так:

ref.takingMethod(new StringFunction() {
    public int func(String param) {
        // body
    }
});

EDIT: в Java 8 вы можете вызвать его с помощью выражения лямбда:

ref.takingMethod(param -> bodyExpression);

Ответ 2

Для каждого "указателя на функцию" я создаю небольшой класс функтора, который будет выполнять ваши вычисления. Определите интерфейс, который будут реализовывать все классы, и передайте экземпляры этих объектов в вашу большую функцию. Это комбинация " шаблон команды" и "шаблон стратегии".

Пример @sblundy хорош.

Ответ 3

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

public enum Operation {
    PLUS {
        public double calc(double a, double b) {
            return a + b;
        }
    },
    TIMES {
        public double calc(double a, double b) {
            return a * b;
        }
    }
     ...

     public abstract double calc(double a, double b);
}

Очевидно, что объявление метода стратегии, а также ровно один экземпляр каждой реализации определены в одном классе/файле.

Ответ 4

Вам нужно создать интерфейс, который предоставляет функции (-и), которые вы хотите передать. например:

/**
 * A simple interface to wrap up a function of one argument.
 * 
 * @author rcreswick
 *
 */
public interface Function1<S, T> {

   /**
    * Evaluates this function on it arguments.
    * 
    * @param a The first argument.
    * @return The result.
    */
   public S eval(T a);

}

Затем, когда вам нужно передать функцию, вы можете реализовать этот интерфейс:

List<Integer> result = CollectionUtilities.map(list,
        new Function1<Integer, Integer>() {
           @Override
           public Integer eval(Integer a) {
              return a * a;
           }
        });

Наконец, функция map использует переданную в Function1 функцию:

   public static <K,R,S,T> Map<K, R> zipWith(Function2<R,S,T> fn, 
         Map<K, S> m1, Map<K, T> m2, Map<K, R> results){
      Set<K> keySet = new HashSet<K>();
      keySet.addAll(m1.keySet());
      keySet.addAll(m2.keySet());

      results.clear();

      for (K key : keySet) {
         results.put(key, fn.eval(m1.get(key), m2.get(key)));
      }
      return results;
   }

Вы можете часто использовать Runnable вместо своего собственного интерфейса, если вам не нужно передавать параметры, или вы можете использовать различные другие методы, чтобы сделать число параметров менее "фиксированным", но обычно это компромисс с безопасностью типа, (Или вы можете переопределить конструктор для того, чтобы ваш объект функции проходил в параметрах таким образом. Есть много подходов, а некоторые работают лучше при определенных обстоятельствах.)

Ответ 5

Вы также можете сделать это (что в некоторых случаях RARE имеет смысл). Проблема (и это большая проблема) заключается в том, что вы теряете все типы безопасности использования класса/интерфейса, и вам приходится иметь дело с тем случаем, когда метод не существует.

У этого есть "преимущество", что вы можете игнорировать ограничения доступа и вызывать частные методы (не показаны в примере, но вы можете вызывать методы, которые компилятор обычно не позволяет вам звонить).

Опять же, это редкий случай, когда это имеет смысл, но в тех случаях это хороший инструмент.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final String methodName;
        final Method method;
        final Main   main;

        main = new Main();

        if(argv.length == 0)
        {
            methodName = "foo";
        }
        else
        {
            methodName = "bar";
        }

        method = Main.class.getDeclaredMethod(methodName, int.class);

        main.car(method, 42);
    }

    private void foo(final int x)
    {
        System.out.println("foo: " + x);
    }

    private void bar(final int x)
    {
        System.out.println("bar: " + x);
    }

    private void car(final Method method,
                     final int    val)
        throws IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        method.invoke(this, val);
    }
}

Ответ 6

Ссылки на методы с использованием оператора ::

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

IntBinaryOperator - это функциональный интерфейс. Его абстрактный метод, applyAsInt, принимает два int в качестве своих параметров и возвращает int. Math.max также принимает два int и возвращает int. В этом примере A.method(Math::max); делает parameter.applyAsInt отправлять свои два входных значения в Math.max и возвращать результат этого Math.max.

import java.util.function.IntBinaryOperator;

class A {
    static void method(IntBinaryOperator parameter) {
        int i = parameter.applyAsInt(7315, 89163);
        System.out.println(i);
    }
}
import java.lang.Math;

class B {
    public static void main(String[] args) {
        A.method(Math::max);
    }
}

В общем, вы можете использовать:

method1(Class1::method2);

вместо:

method1((arg1, arg2) -> Class1.method2(arg1, arg2));

что сокращается для:

method1(new Interface1() {
    int method1(int arg1, int arg2) {
        return Class1.method2(arg1, agr2);
    }
});

Для получения дополнительной информации см. оператор :: (двойной двоеточие) в Java 8 и Java Спецификация языка §15.13.

Ответ 7

Если у вас есть только одна строка, которая отличается, вы можете добавить такой параметр, как флаг и оператор if (flag), который вызывает одну строку или другую.

Ответ 9

@sblundy ответ велик, но анонимные внутренние классы имеют два небольших недостатка, основное из которых заключается в том, что они, как правило, не могут быть повторно использованы, а вторичный - громоздким синтаксисом.

Хорошо, что его шаблон расширяется в полные классы без каких-либо изменений в основном классе (тот, который выполняет вычисления).

Когда вы создаете экземпляр нового класса, вы можете передавать параметры в этот класс, которые могут действовать как константы в вашем уравнении, поэтому, если один из ваших внутренних классов выглядит следующим образом:

f(x,y)=x*y

но иногда вам нужен тот, который:

f(x,y)=x*y*2

и, возможно, третье:

f(x,y)=x*y/2

вместо создания двух анонимных внутренних классов или добавления параметра "сквозной", вы можете создать один класс ACTUAL, который вы создаете как:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f=new InnerFunc(2.0);// for the second
calculateUsing(f);
f=new InnerFunc(0.5);// for the third
calculateUsing(f);

Он просто сохранит константу в классе и будет использовать ее в методе, указанном в интерфейсе.

Фактически, если KNOW, что ваша функция не будет сохранена/использована повторно, вы можете сделать это:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f.setConstant(2.0);
calculateUsing(f);
f.setConstant(0.5);
calculateUsing(f);

Но неизменяемые классы более безопасны - я не могу придумать оправдание, чтобы сделать класс таким, как этот изменяемый.

Я действительно только публикую это, потому что я сжимаю всякий раз, когда слышу анонимный внутренний класс - я видел много избыточного кода, который был "Обязательным", потому что первое, что сделал программист, было анонимным, когда он должен был использовать фактический и никогда не переосмысливали его решение.

Ответ 10

Новые функциональные интерфейсы и ссылки на Java 8 с помощью оператора ::.

Java 8 может поддерживать ссылки на методы (MyClass:: new) с указателями "@Functional Interface". Нет необходимости в том же имени метода, требуется только одна и та же подпись метода.

Пример:

@FunctionalInterface
interface CallbackHandler{
    public void onClick();
}

public class MyClass{
    public void doClick1(){System.out.println("doClick1");;}
    public void doClick2(){System.out.println("doClick2");}
    public CallbackHandler mClickListener = this::doClick;

    public static void main(String[] args) {
        MyClass myObjectInstance = new MyClass();
        CallbackHandler pointer = myObjectInstance::doClick1;
        Runnable pointer2 = myObjectInstance::doClick2;
        pointer.onClick();
        pointer2.run();
    }
}

Итак, что у нас здесь?

  • Функциональный интерфейс - это интерфейс, аннотированный или не связанный с @FunctionalInterface, который содержит только одно объявление метода.
  • Ссылки на методы - это просто специальный синтаксис, выглядит так: objectInstance:: methodName, не более чем ничего.
  • Пример использования - только оператор присваивания, а затем вызов метода интерфейса.

ВЫ ДОЛЖНЫ ИСПОЛЬЗОВАТЬ ФУНКЦИОНАЛЬНЫЕ ИНТЕРФЕЙСЫ ДЛЯ СПИСОК ЛЮБИТЕЛЕЙ ТОЛЬКО И ТОЛЬКО ДЛЯ ЭТОГО!

Потому что все другие такие указатели на функции действительно плохо для чтения кода и для способности понимать. Однако прямые ссылки на методы иногда бывают удобными, например, для foreach.

Существует несколько предопределенных функциональных интерфейсов:

Runnable              -> void run( );
Supplier<T>           -> T get( );
Consumer<T>           -> void accept(T);
Predicate<T>          -> boolean test(T);
UnaryOperator<T>      -> T apply(T);
BinaryOperator<T,U,R> -> R apply(T, U);
Function<T,R>         -> R apply(T);
BiFunction<T,U,R>     -> R apply(T, U);
//... and some more of it ...
Callable<V>           -> V call() throws Exception;
Readable              -> int read(CharBuffer) throws IOException;
AutoCloseable         -> void close() throws Exception;
Iterable<T>           -> Iterator<T> iterator();
Comparable<T>         -> int compareTo(T);
Comparator<T>         -> int compare(T,T);

Для более ранних версий Java вы должны попробовать библиотеки Guava, которые имеют аналогичную функциональность и синтаксис, как упоминал выше Адриан Петреску.

Дополнительные исследования см. Java 8 Cheatsheet

и спасибо The Guy with The Hat за ссылку Java Language Specification §15.13.

Ответ 11

Google библиотеки Guava, которые становятся очень популярными, имеют общий Function и Predicate, что они работали во многих частях своего API.

Ответ 12

Чтобы сделать то же самое без интерфейсов для массива функций:

class NameFuncPair
{
    public String name;                // name each func
    void   f(String x) {}              // stub gets overridden
    public NameFuncPair(String myName) { this.name = myName; }
}

public class ArrayOfFunctions
{
    public static void main(String[] args)
    {
        final A a = new A();
        final B b = new B();

        NameFuncPair[] fArray = new NameFuncPair[]
        {
            new NameFuncPair("A") { @Override void f(String x) { a.g(x); } },
            new NameFuncPair("B") { @Override void f(String x) { b.h(x); } },
        };

        // Go through the whole func list and run the func named "B"
        for (NameFuncPair fInstance : fArray)
        {
            if (fInstance.name.equals("B"))
            {
                fInstance.f(fInstance.name + "(some args)");
            }
        }
    }
}

class A { void g(String args) { System.out.println(args); } }
class B { void h(String args) { System.out.println(args); } }

Ответ 13

Звучит как шаблон стратегии для меня. Проверьте шаблоны Java fluffycat.com.

Ответ 14

Одна из вещей, которые я очень скучаю, когда программирование на Java является функцией обратных вызовов. Одна из ситуаций, когда потребность в них сохранялась, заключалась в рекурсивной обработке иерархии, где вы хотите выполнить некоторые конкретные действия для каждого элемента. Подобно ходу дерева каталогов или обработке структуры данных. Минимализм внутри меня не позволяет определить интерфейс, а затем реализацию для каждого конкретного случая.

Однажды мне стало интересно, почему нет? У нас есть указатели на методы - объект Method. С оптимизацией JIT-компиляторов, рефлексивный вызов действительно не несет огромного штрафа за производительность. И, кроме того, рядом, скажем, копируя файл из одного места в другое, стоимость вызова отраженного метода бледнеет.

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

Посмотрите мое решение на основе отражения для Обратные вызовы в Java. Бесплатно для любого использования.

Ответ 15

oK, эта ветка уже достаточно старая, поэтому очень вероятно, что мой ответ не поможет в вопросе. Но так как этот поток помог мне найти мое решение, я все равно его выложу.

Мне нужно было использовать переменный статический метод с известным входом и известным выходом (оба double). Итак, зная пакет и имя метода, я мог бы работать следующим образом:

java.lang.reflect.Method Function = Class.forName(String classPath).getMethod(String method, Class[] params);

для функции, которая принимает один двойной параметр.

Итак, в моей конкретной ситуации я инициализировал его с помощью

java.lang.reflect.Method Function = Class.forName("be.qan.NN.ActivationFunctions").getMethod("sigmoid", double.class);

и вызывается позже в более сложной ситуации с

return (java.lang.Double)this.Function.invoke(null, args);

java.lang.Object[] args = new java.lang.Object[] {activity};
someOtherFunction() + 234 + (java.lang.Double)Function.invoke(null, args);

где активностью является произвольное двойное значение. Я думаю, что, возможно, сделать это немного абстрактнее и обобщить его, как это сделал SoftwareMonkey, но в настоящее время я достаточно доволен тем, как он есть. Три строки кода, без классов и интерфейсов, это не так уж плохо.

Ответ 16

Отъезд lambdaj

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

и, в частности, его новая функция закрытия

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

и вы найдете очень читаемый способ определения замыкания или указателя функции без создания бессмысленного интерфейса или использования уродливых внутренних классов

Ответ 17

Вау, почему бы просто не создать класс делегата, который не так уж сильно, если я уже сделал это для java и использовал его для передачи в параметр, где T - тип возвращаемого значения. Извините, но как программист на С++/С# вообще, просто изучая java, мне нужны указатели на функции, потому что они очень удобны. Если вы знакомы с любым классом, который занимается информацией о методе, вы можете это сделать. В java-библиотеках, которые будут java.lang.reflect.method.

Если вы всегда используете интерфейс, вам всегда нужно его реализовать. При обработке данных на самом деле нет лучшего способа регистрации/отмены регистрации из списка обработчиков, но для делегатов, где вам нужно передавать функции, а не тип значения, что делает класс делегата обработанным для outclasses интерфейса.

Ответ 18

Если кто-то пытается передать функцию, которая принимает один набор параметров для определения своего поведения, а другой набор параметров для выполнения, например Scheme's:

(define (function scalar1 scalar2)
  (lambda (x) (* x scalar1 scalar2)))

см. Функция передачи с параметризованным поведением в Java

Ответ 19

Начиная с Java8, вы можете использовать lambdas, который также имеет библиотеки в официальном интерфейсе SE 8 API.

Применение: Вам нужно использовать интерфейс только с одним абстрактным методом. Сделайте экземпляр (вы можете использовать один уже предоставленный Java-код 8) следующим образом:

Function<InputType, OutputType> functionname = (inputvariablename) {
... 
return outputinstance;
}

Для получения дополнительной информации проверьте документацию: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

Ответ 20

До появления Java 8 ближайшей заменой функционально-указательным функциям был анонимный класс. Например:

Collections.sort(list, new Comparator<CustomClass>(){
    public int compare(CustomClass a, CustomClass b)
    {
        // Logic to compare objects of class CustomClass which returns int as per contract.
    }
});

Но теперь в Java 8 у нас есть очень опрятная альтернатива, известная как lambda expression, которая может использоваться как:

list.sort((a, b) ->  { a.isBiggerThan(b) } );

где isBiggerThan - это метод в CustomClass. Мы также можем использовать ссылки на методы здесь:

list.sort(MyClass::isBiggerThan);

Ответ 21

Ни один из ответов Java 8 не дал полного, сплоченного примера, так что вот оно.

Объявить метод, который принимает "указатель функции" следующим образом:

void doCalculation(Function<Integer, String> calculation, int parameter) {
    final String result = calculation.apply(parameter);
}

Назовите его, предоставив функцию выражением лямбда:

doCalculation((i) -> i.toString(), 2);