回调函数小对比
JavaScript 中函数是 Function
对象,因此可以很方便的将函数作为参数传递给另一个函数,这大大方便了异步编程。最简单的例子:
function foo(callback) {
if (typeof callback === "function") {
callback("hi there");
}
}
foo(function(param) {
console.log(param);
});
像 addEventListener()
、fetch
都会大量用到回调函数。回调函数的另一个好处在于代码复用更加的灵活,所以回调函数在很多语言中都有方式。恰好最近在重构一段 Java 代码,索性将我了解的几种语言做一次小结。
C
在 C 语言中回调函数是以函数指针(function pointer)的形式体现的。示例代码中 fun_ptr
指向了函数 foo
的地址,自然可以把 fun_ptr
当作变量传递给另一个函数。
#include <stdio.h>
void foo(int i) {
printf("the value of input is %d\n", i);
}
int main() {
void (*fun_ptr)(int) = foo;
(*fun_ptr)(42);
return 0;
}
为了简化书写,实际项目中会使用 typedef
来定义函数指针的原型形式。例如:
#include <stdio.h>
void foo(int i) {
printf("the value of input is %d\n", i);
}
typedef void (*my_handler)(int);
int main() {
my_handler h = foo;
h(42);
return 0;
}
这样函数指针看起来更像是一种类型,避免稍显复杂的语法在使用的时候写错。
C#
C# 中使用的是 delegate
关键字。C# 我很久不用了,在 MSDN 上有很多篇详细的文档,所以这里简单罗列下语法,不展开讨论。
public delegate void my_handler(int i);
public static void foo(int i) {
System.Console.WriteLine("the value of input is {0}", i);
}
my_handler h = foo;
h(42);
对比可以看到 C# 的使用和 C 的函数指针比较相似,且类型声明更为简洁。
Java
好了,回到项目中使用的 Java 了。曾经我对语法糖(syntactic sugar)不以为然,有固然好,没有也无所谓,直到最近…真香 :-)
近年初给另一个项目组提供的库里的方式是定义了一个 interface
,把它作为函数的参数,示例如下:
interface IMyCallback {
void do(int i);
}
class Foo {
public static void foo(int i, IMyCallback cb) {
cb.do(i);
}
}
class MyCallback implements IMyCallback {
@Override
public void do(int i) {
System.out.println("the value of input is " + i);
}
public static void main(String[] args) {
MyCallback cb = new MyCallback();
Foo.foo(42, cb);
}
}
Stackoverflow 上的这个回答 使用了 generic methods,固然更为灵活… 可是语法上还是稍显复杂了一点。
当时的库是在 Java 7 中编写的。最近的项目使用了 Java 8,所以可以借用 Lambda 表达式稍作简化,接着上段代码:
class MyCallbackUsingLambda {
public static void main(String[] args) {
Foo.foo(42, (int i) -> {
System.out.println("the value of input is " + i);
});
}
}
当然 Java 8 中这个 Lambda 表达式写法的回调函数使用也是有点限制的,声名的时候传入的接口(或者类)只能有一个方法,自然这个方法的名字无所谓了。所以这个改进加上 Generic methods 也许会更加灵活易用(?),也许会很混乱,这一点我还没有深入使用,也许受到先入为主的影响,仅从语法层面考虑(也就是忽略底层的实现以及运行效率),C 的函数指针、C# 的 delegate
关键字,或者 JavaScript 的函数对于我来说还是更友好、明确一些。