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


299

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


5
Java 8 будет иметь лямбда-выражения . Вы можете прочитать больше о лямбда-выражениях здесь .
Мариус

4
@Marius Я не совсем думаю, что лямбда-выражения считаются указателями на функции. Оператор, с другой стороны ...::
Парень с Hat

Извините за поздний комментарий;) - обычно вам не нужен указатель на функцию для этого. Просто используйте шаблонный метод! ( en.wikipedia.org/wiki/Template_method_pattern )
isnot2bad

2
@ isnot2bad - глядя на эту статью, кажется излишним - сложнее, чем ответы, приведенные здесь. В частности, шаблонный метод требует создания подкласса для каждого альтернативного расчета. Я не вижу, чтобы OP указывал что-либо, что требует подклассов ; он просто хочет создать несколько методов и поделиться большей частью реализации. Как показывает принятый ответ, это легко сделать «на месте» (внутри каждого метода), даже до Java 8 с его лямбдами.
ToolmakerSteve

@ToolmakerSteve Принятое решение также требует наличия класса для расчета (даже если это просто анонимный внутренний класс). И шаблон шаблонного метода также может быть реализован с использованием анонимных внутренних классов, поэтому он не сильно отличается от принятого решения, касающегося издержек (до Java 8). Так что это больше вопрос модели использования и подробных требований, которые мы не знаем. Я ценю принятый ответ и просто хотел добавить еще одну возможность подумать.
isnot2bad

Ответы:


269

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

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

13
Это пример «Команды Патерна», кстати. en.wikipedia.org/wiki/Command_Pattern
Огр Псалом33

3
@Ogre Psalm33 Этот метод также может быть шаблоном стратегии, в зависимости от того, как вы его используете. Разница между шаблоном стратегии и шаблоном команд .
Рори О'Кейн

2
Вот реализация замыкания для Java 5, 6 и 7 mseifed.blogspot.se/2012/09/… Она содержит все, что можно попросить ... Я думаю, что это довольно круто!
ммм

@SecretService: эта ссылка мертва.
Лоуренс Дол

@LawrenceDol Да, это так. Вот пастбина того класса, который я использую. pastebin.com/b1j3q2Lp
ммм

32

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

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


28

Если в одной строке задано заранее определенное количество различных вычислений, использование 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

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

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


4
Этот «ответ» больше относится к набору задач, чем к набору решений.
tchrist

18

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

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

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

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

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

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

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

1
Я использую это для обработки меню / GUI иногда, потому что синтаксис метода намного проще, чем анонимный синтаксис внутреннего класса. Это аккуратно, но вы добавляете сложность отражения, в которую некоторые люди не хотят вникать, так что будьте чертовски уверены, что вы правильно поняли и имеете четкие текстовые ошибки для каждого возможного состояния ошибки.
Билл К

Вы можете делать это безопасно, используя дженерики, и вам не нужно размышлять.
Луиджи Плинг

1
Я не вижу, как использование обобщений и отсутствие отражения позволит вам вызывать метод по имени, содержащемуся в строке?
TofuBeer

1
@LuigiPlinge - можете ли вы предоставить фрагмент кода того, что вы имеете в виду?
ToolmakerSteve

13

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


1
Ответ javaslook кажется более чистым способом сделать это, если более двух вариантов расчета. Или, если кто-то хочет встроить код в метод, тогда перечисление для различных случаев, которые обрабатывает метод, и переключатель.
ToolmakerSteve

1
@ToolmakerSteve верно, хотя сегодня вы будете использовать лямбды в Java 8.
Питер Лоури,

12

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

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

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


+1 за полезные ссылки, хотя я думаю, что добавление замыканий в Java совершенно бесполезно.
Микера

11

Новые функциональные интерфейсы 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 .


7
« Потому что все остальные ... действительно вредны для читабельности кода » - это совершенно необоснованное утверждение, к тому же неверное.
Лоуренс Дол

9

Ответ @ 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);

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

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


А? ОП говорит о разных вычислениях (алгоритмы; логика); вы показываете разные значения (данные). Вы показываете конкретный случай, когда разница может быть включена в значение, но это неоправданное упрощение поставленной проблемы.
ToolmakerSteve

6

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


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


4

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

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

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

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


4

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

Мне нужно было использовать переменный статический метод с известным вводом и известным выводом (оба 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, но в настоящее время я достаточно доволен тем, как оно есть. Три строки кода, без классов и интерфейсов, это не так уж плохо.


спасибо Робу за добавление codeуценки, я был слишком нетерпелив и глуп, чтобы найти его ;-)
yogibimbi

4

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

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

1
Зачем? Это сложнее, чем предложенные ранее решения, которым просто нужно одно анонимное определение функции для каждой альтернативы В качестве альтернативы вы создаете класс и определение анонимной функции. Хуже того, это делается в двух разных местах кода. Возможно, вы захотите предоставить некоторые обоснования для использования этого подхода.
ToolmakerSteve

3

Проверьте лямбдай

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

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

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

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


3

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

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


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

2

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

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

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

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

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

1

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

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

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


1

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

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

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

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


1

До 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);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.