Какова ближайшая замена для указателя функции в Java? - PullRequest
298 голосов
/ 23 сентября 2008

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

Ответы [ 21 ]

268 голосов
/ 23 сентября 2008

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

Допустим, вы хотите передать функцию с параметром 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);
32 голосов
/ 23 сентября 2008

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

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

28 голосов
/ 27 марта 2009

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

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

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

24 голосов
/ 23 сентября 2008

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

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

16 голосов
/ 24 февраля 2014

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

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

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

15 голосов
/ 27 марта 2009

Вы также можете сделать это (что в некоторых случаях 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);
    }
}
13 голосов
/ 27 марта 2009

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

12 голосов
/ 24 сентября 2008

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

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

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

11 голосов
/ 19 августа 2014

Новый 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();
    }
}

Итак, что мы имеем здесь?

  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 ссылка.

9 голосов
/ 04 мая 2010

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

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

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

f(x,y)=x*y

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

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

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

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

вместо того, чтобы создавать два анонимных внутренних класса или добавлять параметр "passthrough", вы можете создать один класс 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);

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

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

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

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

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

...