什么是Java中函数指针的最接近的替代品?

时间:2008-09-23 17:20:11

标签: java closures function-pointers

我有一个大约十行代码的方法。我想创建更多完全相同的方法,除了一个会改变一行代码的小计算。这是传递函数指针以替换该行的完美应用程序,但Java没有函数指针。什么是我最好的选择?

22 个答案:

答案 0 :(得分:268)

匿名内部课程

假设您希望通过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中,您可以使用lambda表达式调用它:

ref.takingMethod(param -> bodyExpression);

答案 1 :(得分:32)

对于每个“函数指针”,我将创建一个实现计算的小functor class。 定义所有类将实现的接口,并将这些对象的实例传递给更大的函数。这是“command pattern”和“strategy pattern”的组合。

@ sblundy的例子很好。

答案 2 :(得分:28)

如果您可以在该行中执行预定义数量的不同计算,则使用枚举是一种快速但清晰的方法来实现策略模式。

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

显然,策略方法声明以及每个实现的一个实例都是在单个类/文件中定义的。

答案 3 :(得分: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而不是您自己的界面,或者您可以使用各种其他技术使参数计数不那么“固定”,但它通常是类型安全的权衡。 (或者你可以覆盖你的函数对象的构造函数,以这种方式传递params ..有很多方法,有些方法在某些情况下更好。)

答案 4 :(得分:16)

使用::运算符

的方法引用

您可以在方法接受功能接口的方法参数中使用方法引用。功能接口是仅包含一个抽象方法的任何接口。 (功能接口可能包含一个或多个默认方法或静态方法。)

IntBinaryOperator是一个功能界面。它的抽象方法applyAsInt接受两个int作为参数,并返回intMath.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);
    }
});

有关详细信息,请参阅:: (double colon) operator in Java 8Java Language Specification §15.13

答案 5 :(得分:15)

你也可以这样做(在某些 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 :(得分:13)

如果你只有一行不同,你可以添加一个参数,例如一个标志和一个调用一行或另一行的if(flag)语句。

答案 7 :(得分:12)

答案 8 :(得分:11)

使用::运算符的新Java 8 功能接口方法参考

Java 8能够使用&#34; @ Functional Interface &#34;维护方法引用(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. 用法示例 - 只是一个赋值运算符,然后是接口方法调用。
  4. 你应该只为那些听众使用功能接口,而且只能这样做!

    因为所有其他此类函数指针对于代码可读性和理解能力都非常糟糕。但是,直接的方法引用有时会很方便,例如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 Libraries,它具有与Adrian Petrescu上面提到的类似的功能和语法。

    有关其他研究,请查看Java 8 Cheatsheet

    感谢The Guy with the Hat for Java Language Specification §15.13 link。

答案 9 :(得分: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);

但是不可变的类更安全 - 我无法想出使这样的类变得可变的理由。

我真的只发​​布这个因为每当我听到匿名的内部课时我都会畏缩 - 我看到很多冗余的代码都是“必需的”,因为程序员做的第一件事就是当他应该使用实际的时候是匿名的上课,从不重新考虑他的决定。

答案 10 :(得分:6)

Google Guava libraries变得非常受欢迎,它们有一个通用的FunctionPredicate对象,他们已经在API的很多部分工作了。

答案 11 :(得分:4)

在Java编程时我真正想念的一件事是函数回调。一种情况是,这些需要保持自我呈现的是递归处理层次结构,您希望为每个项目执行某些特定操作。就像走一个目录树,或者处理数据结构一样。我内心的极简主义者不得不为每个特定情况定义一个接口,然后是一个实现。

有一天,我发现自己想知道为什么不呢?我们有方法指针 - Method对象。通过优化JIT编译器,反射调用实际上不再带来巨大的性能损失。除了旁边,比如说,将文件从一个位置复制到另一个位置,反映的方法调用的成本就变得无足轻重。

当我更多地考虑它时,我意识到OOP范例中的回调需要将对象和方法绑定在一起 - 输入Callback对象。

查看基于反射的Callbacks in Java解决方案。免费使用。

答案 12 :(得分: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); } }

答案 13 :(得分:4)

oK,这个帖子已经足够老了,所以很可能我的答案对这个问题没有帮助。但是由于这个线程帮助我找到了我的解决方案,无论如何我都会把它放在这里。

我需要使用具有已知输入和已知输出的可变静态方法( double )。那么,知道方法包和名称,我可以按如下方式工作:

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

表示接受一个double作为参数的函数。

所以,在我的具体情况下,我用

初始化它
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是任意的double值。正如SoftwareMonkey所做的那样,我正在考虑做一些更抽象和概括的事情,但目前我很满意它的方式。三行代码,没有必要的类和接口,这不是太糟糕。

答案 14 :(得分:4)

听起来像是一种策略模式。查看fluffycat.com Java模式。

答案 15 :(得分:3)

哇,为什么不创建一个Delegate类,并不是我已经为java做过的那么难,并使用它来传递参数,其中T是返回类型。我很抱歉,作为一名C ++ / C#程序员,一般只是学习java,我需要函数指针,因为它们非常方便。如果您熟悉任何处理方法信息的课程,您可以这样做。在java库中,它将是java.lang.reflect.method。

如果您始终使用界面,则始终必须实施该界面。在事件处理中,从处理程序列表中注册/取消注册实际上没有更好的方法,但是对于需要传递函数而不是值类型的委托,使委托类处理外部接口的类。

答案 16 :(得分:3)

查看lambdaj

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

,特别是它的新闭包功能

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

你会发现一种非常易读的方法来定义闭包或函数指针而不创建无意义的接口或使用丑陋的内部类

答案 17 :(得分:2)

Java 8的答案都没有给出一个完整的,有凝聚力的例子,所以它来了。

声明接受“函数指针”的方法,如下所示:

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

通过为函数提供lambda表达式来调用它:

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

答案 18 :(得分:1)

如果有人正在努力传递一个函数,该函数需要一组参数来定义它的行为,而另一组参数要执行,比如Scheme的:

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

请参阅Pass Function with Parameter-Defined Behavior in Java

答案 19 :(得分:1)

从Java8开始,您可以使用lambdas,它也在官方SE 8 API中有库。

<强>用法: 您需要使用只有一个抽象方法的接口。 制作一个实例(你可能想要使用已提供的java SE 8),如下所示:

c = C()
a = c.a

有关详细信息,请查看文档:{​​{3}}

答案 20 :(得分: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中,我们有一个非常简洁的替代方案lambda expression,可以用作:

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

其中isBiggerThan是CustomClass中的方法。我们也可以在这里使用方法参考:

list.sort(MyClass::isBiggerThan);

答案 21 :(得分:0)

开源safety-mirror项目将上述解决方案中的一些归纳为一个库,该库向Java添加了函数,委托和事件。

有关备忘单的功能,请参见自述文件或this stackoverflow answer

对于函数,该库引入了一个Fun接口,以及一些子接口(与泛型一起),这些子接口构成了将方法用作类型的流畅API。

Fun.With0Params<String> myFunctionField = "   hello world   "::trim;`  
Fun.With2Params<Boolean, Object, Object> equals = Objects::equals;`  
    
public void foo(Fun.With1ParamAndVoid<String> printer) throws Exception {
    printer.invoke("hello world);
}  

public void test(){
    foo(System.out::println);
}  

注意:

  1. 您必须选择与目标签名中的参数数量相匹配的子接口。 Fx,如果有一个参数,则选择Fun.With1Param。
  2. 泛型用于定义A)返回类型和B)签名的参数。

此外,请注意传递给foo()方法的调用的方法引用的签名必须与方法Foo定义的Fun匹配。否则,编译器将发出错误。