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

What's the nearest substitute for a function pointer in Java?

提问人:Bill the Lizard 提问时间:9/24/2008 最后编辑:Bill the Lizard 更新时间:9/7/2020 访问量:92420

问:

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

Java 闭包函数 指针

评论

5赞 Marius 3/21/2013
Java 8 将具有 Lambda 表达式。您可以在此处阅读有关 lambda 表达式的更多信息。
5赞 The Guy with The Hat 4/4/2014
@Marius 我不太认为 lambda 表达式算作函数指针。另一方面,:: 运算符...
1赞 isnot2bad 6/26/2014
很抱歉评论;)晚了 - 通常,您不需要函数指针。只需使用模板方法即可!(en.wikipedia.org/wiki/Template_method_pattern)
3赞 ToolmakerSteve 9/5/2015
@isnot2bad - 看那篇文章,似乎有点矫枉过正 - 比这里给出的答案更复杂。具体来说,模板方法需要为每个备选计算创建一个子类。我没有看到 OP 陈述了任何需要子类的内容;他只是想创建几个方法,并共享大部分实现。正如公认的答案所显示的那样,这很容易在“就地”(在每个方法中)完成,甚至在 Java 8 及其 lambda 之前。
0赞 isnot2bad 9/7/2015
@ToolmakerSteve 接受的解决方案还需要每个计算的类(即使它只是一个匿名的内部类)。模板方法模式也可以使用匿名内部类来实现,因此它与公认的开销解决方案(在 Java 8 之前)没有太大区别。因此,这更多的是使用模式和详细要求的问题,我们不知道。我很欣赏被接受的答案,只是想增加另一种可能性。

答:

273赞 sblundy 9/24/2008 #1

匿名内部类

假设您希望传递一个函数,其中包含返回 .
首先,如果不能重用现有接口,则必须定义一个接口,该接口作为其唯一成员。
Stringint

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

评论

13赞 Ogre Psalm33 10/10/2008
顺便说一句,这是“命令父辈”的一个例子。en.wikipedia.org/wiki/Command_Pattern
3赞 Rory O'Kane 12/31/2011
@Ogre 诗篇33 这种技术也可以是策略模式,这取决于你如何使用它。策略模式和命令模式之间的区别
2赞 mjs 9/30/2012
这是 Java 5、6 和 7 mseifed.blogspot.se/2012/09/ 的闭包实现......它包含了人们可以要求的一切......我觉得这真是太棒了!
0赞 Lawrence Dol 9/18/2014
@SecretService:这个链接已经死了。
0赞 mjs 9/23/2014
@LawrenceDol是的,是的。这是我正在使用的类的粘贴。pastebin.com/b1j3q2Lp
33赞 Blair Conrad 9/24/2008 #2

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

@sblundy的榜样很好。

24赞 rcreswick 9/24/2008 #3

您需要创建一个接口来提供要传递的函数。例如:

/**
 * 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 12/1/2010
这个“答案”更多地与问题集有关,而不是与解决方案集有关。
12赞 Dave L. 9/24/2008 #4

您可能还有兴趣了解 Java 7 中涉及闭包的工作:

Java 中闭包的现状如何?


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

评论

0赞 mikera 7/1/2010
+1 用于有用的链接,即使我认为向 Java 添加闭包是完全没有帮助的。
4赞 Dennis S 9/25/2008 #5

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

15赞 TofuBeer 3/27/2009 #6

您也可以这样做(在某些罕见的场合是有道理的)。问题(这是一个大问题)是你失去了使用类/接口的所有类型安全,你必须处理方法不存在的情况。

它确实有一个“好处”,即您可以忽略访问限制并调用私有方法(示例中未显示,但您可以调用编译器通常不允许调用的方法)。

同样,这在极少数情况下是有道理的,但在那些情况下,它是一个很好的工具。

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赞 Bill K 5/5/2010
我有时将其用于菜单处理/GUI,因为方法语法比匿名内部类语法简单得多。它很整洁,但你增加了一些人不想深入研究的反射的复杂性,所以要确保你做对了,并且对每个可能的错误条件都有明确的文本错误。
0赞 Luigi Plinge 8/5/2011
您可以使用泛型安全地键入,并且不需要反射。
1赞 TofuBeer 9/6/2011
我不明白使用泛型和不使用反射如何让您通过 String 中包含的名称调用方法?
1赞 ToolmakerSteve 9/5/2015
@LuigiPlinge - 你能提供你的意思的代码片段吗?
13赞 Peter Lawrey 3/27/2009 #7

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

评论

1赞 ToolmakerSteve 9/5/2015
javaslook 的答案似乎是一种更简洁的方法,如果有两个以上的计算变体。或者,如果希望将代码嵌入到方法中,则该方法处理的不同情况的枚举和开关。
1赞 Peter Lawrey 9/5/2015
@ToolmakerSteve真的,尽管今天你会在 Java 8 中使用 lambda。
29赞 javashlook 3/27/2009 #8

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

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赞 Mario Fusco 9/12/2009 #9

查看 lambdaj

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

特别是其新的闭合功能

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

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

5赞 Lawrence Dol 1/28/2010 #10

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

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

随着我思考得越来越多,我意识到 OOP 范式中的回调需要将对象和方法绑定在一起 - 输入 Callback 对象。

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

9赞 Bill K 5/5/2010 #11

@sblundy的答案很好,但匿名内部类有两个小缺陷,首先是它们往往不可重用,其次是语法笨重。

好消息是,他的模式可以扩展到完整的类中,而主类(执行计算的类)没有任何变化。

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

f(x,y)=x*y

但有时你需要一个:

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

也许还有第三个是:

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

您可以创建单个实例化为的 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);

但是不可变类更安全——我想不出一个理由来使这样的类变得可变。

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

评论

0赞 ToolmakerSteve 9/5/2015
哼?OP谈论的是不同的计算(算法;逻辑);您显示的是不同的(数据)。你确实展示了一个特定的案例,其中差异可以合并到一个值中,但这是对所提出问题的不合理的简化。
6赞 Adrian Petrescu 12/17/2010 #12

Google Guava 库变得非常流行,它有一个通用的 FunctionPredicate 对象,它们已经将其用于其 API 的许多部分。

评论

0赞 ToolmakerSteve 9/5/2015
如果这个答案提供了代码细节,它会更有用。获取已接受答案中显示的代码,并使用 Function 显示它的外观。
3赞 Robert 3/2/2011 #13

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

如果你总是使用一个接口,你总是必须实现它。在事件处理中,确实没有更好的方法可以从处理程序列表中注册/注销,但对于需要传入函数而不是值类型的委托,制作一个委托类来处理它,以处理外级接口。

评论

0赞 ToolmakerSteve 9/5/2015
除非您显示代码详细信息,否则这不是一个有用的答案。创建 Delegate 类有什么帮助?每个备选方案需要什么代码?
4赞 vwvan 3/18/2011 #14

要在没有函数数组接口的情况下执行相同的操作:

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 9/5/2015
为什么?这比以前提出的解决方案更复杂,后者只需要每个备选方案一个匿名函数定义。根据替代方法,您可以创建一个类和一个匿名函数定义。更糟糕的是,这是在代码中的两个不同位置完成的。您可能希望提供一些使用此方法的理由。
4赞 yogibimbi 11/9/2011 #15

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

我需要使用具有已知输入和已知输出(均为双精度)的可变静态方法。因此,在知道方法包和名称后,我可以按如下方式工作:

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 是任意双精度值。我正在考虑也许可以像 SoftwareMonkey 那样更抽象地概括它,但目前我对它的方式感到满意。三行代码,不需要类和接口,这还不错。

评论

0赞 yogibimbi 11/18/2011
感谢 Rob 添加 markdown,我太不耐烦和愚蠢了,找不到它;code
19赞 The Guy with The Hat 2/24/2014 #16

使用运算符的方法引用::

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

IntBinaryOperator 是一个函数式接口。它的抽象方法 applyAsInt 接受两个 s 作为其参数,并返回一个 .Math.max 也接受两个 s 并返回一个 .在此示例中,make 将其两个输入值发送到 并返回 .intintintintA.method(Math::max);parameter.applyAsIntMath.maxMath.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

1赞 Scott Emmons 6/11/2014 #17

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

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

请参阅 Java 中使用参数定义行为传递函数

11赞 user3002379 8/19/2014 #18

使用 : 运算符的新 Java 8 函数接口方法引用

Java 8 能够维护带有“@ Functional Interface”指针的方法引用 ( 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. 用法示例 - 只是一个赋值运算符,然后调用接口方法。

您应该仅对侦听器使用功能接口,并且仅为此而使用!

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

有关其他研究,请查看 Java 8 备忘单

感谢 The Guy with The Hat 提供的 Java 语言规范 §15.13 链接。

评论

8赞 Lawrence Dol 9/18/2014
"因为所有其他......对代码的可读性真的很不利“是一个完全没有根据的说法,而且是错误的。
1赞 Alex 5/10/2015 #19

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

用法:您需要使用一个仅具有一个抽象方法的接口。 制作一个实例(您可能希望使用已经提供的 java SE 8 实例),如下所示:

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

有关更多信息,请查看文档:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

1赞 akhil_mittal 6/23/2015 #20

在 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 表达式,它可以用作:

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

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

list.sort(MyClass::isBiggerThan);
3赞 mrts 10/8/2016 #21

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

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

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

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

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