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

StackOverflow https://stackoverflow.com/questions/122407

Вопрос

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

Это было полезно?

Решение

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

Предположим, вы хотите передать функцию с помощью 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
    }
});

РЕДАКТИРОВАТЬ: В 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;
           }
        });

Наконец, функция карты использует переданное в 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 вместо собственного интерфейса, если вам не нужно передавать параметры, или вы можете использовать различные другие методы, чтобы сделать счетчик параметров менее «фиксированным», но обычно это компромисс с безопасностью типов.(Или вы можете переопределить конструктор для вашего функционального объекта, чтобы передать параметры таким образом..существует множество подходов, и некоторые из них работают лучше в определенных обстоятельствах.)

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

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

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.

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

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

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

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

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

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

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

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

Новая Ява 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::имяметода, ни больше ни меньше.
  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

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

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

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

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

f(x,y)=x*y

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

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

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

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

вместо того, чтобы создавать два анонимных внутренних класса или добавлять «сквозной» параметр, вы можете создать один ФАКТИЧЕСКИЙ класс, экземпляр которого вы создадите как:

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.

Для меня это звучит как образец стратегии.Ознакомьтесь с шаблонами Java на сайте fluffycat.com.

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

Однажды я задумался, почему бы и нет?У нас есть указатели на методы — объект Method.Благодаря оптимизации 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);

где активность — произвольное двойное значение.Я подумываю о том, чтобы сделать это немного более абстрактно и обобщить, как это сделал 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); } }

Посмотрите лямбдадж

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

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

http://code.google.com/p/lambdaj/wiki/Замыкания

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

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

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

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

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

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

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

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

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

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

видеть Функция Pass с поведением, определяемым параметрами, в Java

Начиная с Java8, вы можете использовать лямбды, библиотеки которых также есть в официальном 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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top