Java中的函数指针


148

这可能是常见且琐碎的事情,但我似乎很难找到具体答案。在C#中,有一个委托的概念,它与C ++中的函数指针紧密相关。Java中是否有类似的功能?鉴于缺少指针,最好的方法是什么?需要明确的是,我们在这里谈论头等舱。


1
我只是想知道为什么要在侦听器或其他OOP构造在保留对象性质的同时执行相同任务的原因。我的意思是我理解该概念提供的功能的需求,就是可以通过普通对象实现该功能。.除非,当然,我丢失了某些东西,因此我提出了这个问题!:-)
Newtopian

2
Java 8具有lambda表达式:docs.oracle.com/javase/tutorial/java/javaOO / ...您可能想要检查一下。并不是一个函数指针,但可能仍有更多用途。
FrustratedWithFormsDesigner 2014年

6
Java 8方法引用正是您所要的。
史蒂文

8
Java 8 方法引用正是您所要的。 this::myMethod在语义上与创建lambda相同paramA, paramB -> this.myMethod(paramA, paramB)
史蒂文2014年

Answers:


126

类似函数指针的功能的Java习惯用法是实现接口的匿名类,例如

Collections.sort(list, new Comparator<MyClass>(){
    public int compare(MyClass a, MyClass b)
    {
        // compare objects
    }
});

更新:以上是Java 8之前的Java版本中必需的。现在,我们有更好的替代方法,即lambda:

list.sort((a, b) -> a.isGreaterThan(b));

和方法参考:

list.sort(MyClass::isGreaterThan);

2
策略设计模式。当您希望函数使用一些未提供给函数实参的非全局数据时,它不像C或C ++函数指针那样丑陋。
Raedwald

75
@Raedwald C ++具有函子,而C ++ 0x具有在函子之上构建的闭包和lambda。它甚至具有std::bind将参数绑定到函数并返回可调用对象的。我不能防守C对这些理由,但C ++真的比Java为了这个美好的。
zneak 2011年

21
@ zneak,@ Raedwald:您可以在C语言中通过传递指向用户数据的指针(例如:struct)来执行此操作。(并且可以将其封装在每个新级别的回调间接中)实际上很干净。
brice 2011年

25
是的,实际上,我实际上每天都会使用C函数指针来处理粗糙的包装类
BT

3
Java 8为此具有更好的语法。
托尔比约恩Ravn的安德森

65

您可以用接口替换函数指针。假设您要遍历一个集合并对每个元素执行某项操作。

public interface IFunction {
  public void execute(Object o);
}

这是我们可以传递给诸如CollectionUtils2.doFunc(Collection c,IFunction f)的接口。

public static void doFunc(Collection c, IFunction f) {
   for (Object o : c) {
      f.execute(o);
   }
}

例如,我们有一个数字集合,您想为每个元素加1。

CollectionUtils2.doFunc(List numbers, new IFunction() {
    public void execute(Object o) {
       Integer anInt = (Integer) o;
       anInt++;
    }
});

42

您可以使用反射来做到这一点。

将对象和方法名称(作为字符串)作为参数传递,然后调用该方法。例如:

Object methodCaller(Object theObject, String methodName) {
   return theObject.getClass().getMethod(methodName).invoke(theObject);
   // Catch the exceptions
}

然后按以下方式使用它:

String theDescription = methodCaller(object1, "toString");
Class theClass = methodCaller(object2, "getClass");

当然,请检查所有异常并添加所需的强制类型转换。


7
反射不是对任何东西的正确答案,除了“我如何编写缓慢的狡猾的Java代码”:D
Jacob

5
它可能是“丑陋的”且缓慢,但是我相信反射允许做一些公认的答案中基于接口的解决方案无法做的事情(除非我弄错了),即在同一代码中调用同一对象的各种方法一部分。
Eusebius 2014年

@zoquete ..我正在阅读这篇文章,并有一个疑问..所以在您的方法中,如果我访问变量“ theDescription”,则每次访问该变量时都会调用该函数?
karthik27 2014年

20

不,函数不是Java中的一流对象。您可以通过实现处理程序类来做同样的事情-这就是在Swing等中实现回调的方式。

但是,在Java的未来版本中有闭包的建议(您正在谈论的正式名称)-Javaworld有一篇有趣的文章。


15

这让我想起了史蒂夫·叶格(Steve Yegge)在名词王国中处决。它基本上声明Java的每个动作都需要一个对象,因此没有像函数指针这样的“仅动词”实体。


8

为了实现类似的功能,您可以使用匿名内部类。

如果要定义接口Foo

interface Foo {
    Object myFunc(Object arg);
}

创建一个bar将接收“函数指针”作为参数的方法:

public void bar(Foo foo) {
    // .....
    Object object = foo.myFunc(argValue);
    // .....
}

最后调用该方法,如下所示:

bar(new Foo() {
    public Object myFunc(Object arg) {
        // Function code.
    }
}

7

Java8引入了lambda和方法引用。因此,如果您的函数与功能接口匹配(可以创建自己的接口),则在这种情况下可以使用方法引用。

Java提供了一组通用的功能接口。而您可以执行以下操作:

public class Test {
   public void test1(Integer i) {}
   public void test2(Integer i) {}
   public void consumer(Consumer<Integer> a) {
     a.accept(10);
   }
   public void provideConsumer() {
     consumer(this::test1);   // method reference
     consumer(x -> test2(x)); // lambda
   }
}

是否存在以下概念alias:例如 public List<T> collect(T t) = Collections::collect
javadba

据我所知@javadba。
亚历克斯

5

Java中没有这样的东西。您将需要将函数包装到某个对象中,并将引用传递给该对象,以便将该引用传递给该对象上的方法。

从句法上讲,可以通过使用就地定义的匿名类或定义为该类的成员变量的匿名类在一定程度上缓解这种情况。

例:

class MyComponent extends JPanel {
    private JButton button;
    public MyComponent() {
        button = new JButton("click me");
        button.addActionListener(buttonAction);
        add(button);
    }

    private ActionListener buttonAction = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // handle the event...
            // note how the handler instance can access 
            // members of the surrounding class
            button.setText("you clicked me");
        }
    }
}

3

我已经使用反射在Java中实现了回调/委托支持。详细信息和工作源可在我的网站上找到

这个怎么运作

我们有一个名为Callback的主体类,以及一个名为WithParms的嵌套类。需要回调的API将把Callback对象作为参数,并在必要时创建一个Callback.WithParms作为方法变量。由于此对象的许多应用程序都是递归的,因此可以很干净地工作。

由于性能仍然是我的重中之重,因此我不需要创建一个一次性对象数组来保存每次调用的参数-毕竟,在大型数据结构中,可能有成千上万的元素,并且在消息处理中在这种情况下,我们最终可能每秒处理数千个数据结构。

为了确保线程安全,参数数组必须在API方法的每次调用中唯一存在,并且为了提高效率,应在回调的每次调用中使用相同的参数数组。我需要第二个对象,该对象创建起来很便宜,以便将回调与参数数组绑定以进行调用。但是,在某些情况下,由于其他原因,调用者将已经具有参数数组。由于这两个原因,参数数组不属于Callback对象。同样,调用的选择(将参数作为数组或作为单个对象传递)也使用回调在API手中,从而使它能够使用最适合其内部工作方式的任何调用。

然后,WithParms嵌套类是可选的,有两个作用,它包含回调调用所需的参数对象数组,并提供10个重载invoke()方法(具有1到10个参数),这些方法将加载参数数组,然后调用回调目标。



-1

相对于这里的大多数人,我是Java的新手,但是由于我没有看到类似的建议,因此我有另一种建议。我不确定这是否是一个好的做法,或者甚至以前没有建议过,但我只是没有得到。我喜欢它,因为我认为它具有自我描述性。

 /*Just to merge functions in a common name*/
 public class CustomFunction{ 
 public CustomFunction(){}
 }

 /*Actual functions*/
 public class Function1 extends CustomFunction{
 public Function1(){}
 public void execute(){...something here...}
 }

 public class Function2 extends CustomFunction{
 public Function2(){}
 public void execute(){...something here...}
 }

 .....
 /*in Main class*/
 CustomFunction functionpointer = null;

然后根据应用程序分配

 functionpointer = new Function1();
 functionpointer = new Function2();

等等

并致电

 functionpointer.execute();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.