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

时间:2020-03-06 14:36:46  来源:igfitidea点击:

我有一个大约十行代码的方法。我想创建更多功能完全相同的方法,只是进行少量计算会更改一行代码。这是用于传递函数指针以替换该行的完美应用程序,但是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中,我们可以使用lambda表达式来调用它:

ref.takingMethod(param -> bodyExpression);

对于每个"函数指针",我将创建一个小的函子类来实现计算。
定义一个所有类都将实现的接口,并将这些对象的实例传递到更大的函数中。这是"命令模式"和"策略模式"的组合。

@sblundy的例子很好。

我们需要创建一个接口,以提供要传递的功能。例如:

/**
 * 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的"固定"数量,但这通常是在类型安全性方面的权衡。 (或者,我们可以重写构造函数,以使函数对象以这种方式传递参数。

我们可能还想了解有关Java 7正在进行的涉及闭包的工作:

Java中闭包的当前状态是什么?

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

对我来说,这听起来像是一种策略模式。查看fluffycat.com Java模式。

我们也可以执行此操作(在某些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);
    }
}

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

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

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

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

签出lambdaj

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

特别是其新的关闭功能

http://code.google.com/p/lambdaj/wiki/关闭

并且我们将找到一种非常容易理解的方式来定义闭包或者函数指针,而无需创建无意义的接口或者使用丑陋的内部类

使用Java进行编程时,我真正想念的一件事是函数回调。需要这些内容不断显示的一种情况是递归处理层次结构,在该层次结构中我们想对每个项目执行一些特定的操作。就像遍历目录树或者处理数据结构一样。我内心的极简主义者讨厌必须为每种特定情况定义一个接口,然后定义一个实现。

有一天,我发现自己在想为什么不呢?我们有方法对象的方法指针。通过优化JIT编译器,反射调用确实不再带来巨大的性能损失。除了将文件从一个位置复制到另一个位置之外,所反映的方法调用的代价也微不足道。

当我进一步考虑时,我意识到OOP范式中的回调需要将一个对象和一个方法绑定在一起,以输入Callback对象。

看看我基于反射的Java回调解决方案。免费使用。

@sblundy的答案很好,但是匿名内部类有两个小缺陷,主要的缺陷是它们往往不可重用,而次要的则是庞大的语法。

令人高兴的是,他的模式可以扩展为完整的类,而对主类(执行计算的那个类)没有任何更改。

实例化一个新类时,可以将参数传递给该类,该参数可以在方程式中充当常量-因此,如果一个内部类如下所示:

f(x,y)=x*y

但有时我们需要的是:

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

也许三分之一是:

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

我们可以创建一个实例化为单个的ACTUAL类,而不是创建两个匿名内部类或者添加" 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 Guava库具有通用的Function和Predicate对象,它们已在其API的许多部分中使用。