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

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

21 ответов


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

скажем, вы хотите, чтобы функция была передана с помощью String param, который возвращает 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);

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

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


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

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

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


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

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

   /**
    * Evaluates this function on it's 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 вместо вашего собственного интерфейса, если вам не нужно передавать параметры, или вы можете использовать различные другие методы, чтобы сделать счетчик param меньше "исправлено", но обычно это компромисс с безопасностью типа. (Или вы можете переопределить конструктор для вашего объекта функции, чтобы передать параметры таким образом.. существует множество подходов, и некоторые из них работают лучше в определенных обстоятельствах.)


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

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

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

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

ссылки на метод с помощью :: оператор

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

IntBinaryOperator функциональный интерфейс. Его абстрактный метод, applyAsInt, принимает два ints в качестве его параметров и возвращает int. Math.max также принимает два ints и возвращает 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.


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


Вам также может быть интересно услышать о работе, проводимой для Java 7 с использованием замыканий:

каково текущее состояние замыканий в Java?

http://gafter.blogspot.com/2006/08/closures-for-java.html
http://tech.puredanger.com/java7/#closures


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

Java 8 способен поддерживать ссылки на методы (MyClass::new ) с помощью "@ Функционального Интерфейса" указатели. Нет необходимости в том же имени метода, требуется только та же подпись метода.

пример:

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

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

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

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

потому что все другие такие указатели функций действительно плохи для читаемости кода и для способности понимать. Однако прямые ссылки на методы иногда пригодятся, например, с 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

и спасибо парню в шляпе за Спецификация Языка Java §15.13 ссылка.


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

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

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

f(x,y)=x*y

но иногда вам нужна:

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

и, может быть, третий, который:

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

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

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

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

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

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

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

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


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


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


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

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

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

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


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

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

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

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


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

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

Проверьте lambdaj

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

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

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

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


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

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


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

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

посмотреть передайте функцию с параметром-определенным поведением в Java


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

использование: Вам нужно использовать интерфейс только с одним абстрактным методом. Сделайте экземпляр этого (вы можете использовать один java SE 8, уже предоставленный), как это:

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

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


до 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 у нас есть очень аккуратная альтернатива, известная как лямбда-выражение, который можно использовать как:

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

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

list.sort(MyClass::isBiggerThan);

ни один из ответов Java 8 не дал полного, Связного примера,поэтому вот он.

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

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

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

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