阅读本篇文章需要的前置知识

  • C 语言函数指针的定义方式
  • 指针的概念和基本用法
  • Java 中接口的实现和定义
  • Java 匿名类的使用

C 语言实现回调

C 语言中的回调指的是回调函数,即通过函数指针的方式将函数指针传递给调用者(某个函数),调用者在使用时就如同使用一个参数那样去调用函数。

下面的示例展示了函数指针的基本用法以及回调的实现

#include <stdio.h>

// 自定义的函数,作为参数传入调用者的函数中
void my_callback_func() {
    printf("我是回调函数,以函数指针的形式传入别的函数中以供其他函数调用我、n");
}

// 回调函数的调用者,通过参数中传递来的函数指针去调用回调函数
void invoker(void(*callback)(void)) {
    callback();
}

int main() {
    // 函数指针的定义与用法
    void (*callback)(void) = my_callback_func;
    callback();
    
    // 传入函数指针实现回调
    invoker(callback);
    
    return 0;
}

Java 实现回调

C 语言通过函数指针实现回调的方式十分便捷,然而 Java 中没有指针这一概念,更何谈指针函数。因此,想要在 Java 中实现回调只能另辟蹊径。

Java 想要传入函数供其他对象的函数去调用,最容易想到的方法即把包含函数的对象本身作为字段定义在调用者的类中,使用时调用类内的该字段对应的方法即可。

下面是这种实现的一个示例

CallbackProvider

package callback.example;

// CallbackInvoker 类,回调函数的提供者。

public class CallbackProvider {
    public void myCallbackMethod() {
        System.out.println("我是回调函数,看到此处说明我作为回调函数被调用了");
    }
}

CallbackInvoker

package callback.example;

// CallbackInvoker 类,回调函数的调用者。

public class CallbackInvoker {
    // 作为字段定义的一个回调提供者
    private CallbackProvider callbackProvider;

    public CallbackInvoker() {

    }

    public CallbackInvoker(CallbackProvider provider) {
        this.callbackProvider = provider;
    }

    public void setCallbackProvider(CallbackProvider callbackProvider) {
        this.callbackProvider = callbackProvider;
    }

    public void invoke(){
        System.out.println("在调用回调函数之前做一些事情");

        // 调用回调函数
        callbackProvider.myCallbackMethod();

        System.out.println("在调用回调函数之后做一些事情");
    }

}

main函数

import callback.example.CallbackInvoker;
import callback.example.CallbackProvider;

public class Main {
    public static void main(String[] args) {
        // 定义回调函数的提供者与调用者
        CallbackProvider provider = new CallbackProvider();
        CallbackInvoker invoker = new CallbackInvoker();

        // 对调用者传入回调函数的对应的对象
        invoker.setCallbackProvider(provider);
        // 调用者触发回调函数
        invoker.invoke();
    }
}

main函数执行结果

在调用回调函数之前做一些事情
我是回调函数,看到此处说明我作为回调函数被调用了
在调用回调函数之后做一些事情

上述方法看似实现了类似回调的结果,但是应当注意到的是,回调函数的调用者和提供者紧紧的耦合在了一起,调用者想要使用多种不同的回调函数需要编写多个额外的回调函数提供者类。可见这样的实现十分不优雅,没有发挥 Java 语言本身的特性。这种把具体函数抽象起来的需求可以通过接口来实现。

下面通过接口提供了一种更通用的实现方式

首先定义一个描述回调方法的接口ICallback

package callback.example;

// 自定义的回调接口
public interface ICallback {
    void callbackMethod();
}

然后让CallbackProvider类实现接口

package callback.example;

// CallbackInvoker 类,回调函数的提供者。

public class CallbackProvider implements ICallback {
    @Override
    public void callbackMethod() {
        System.out.println("我是回调函数,看到此处说明我作为回调函数被调用了");
    }
}

最后改写CallbackInvoker

package callback.example;

// CallbackInvoker 类,回调函数的调用者。

import javax.security.auth.callback.Callback;

public class CallbackInvoker {

    public CallbackInvoker() {

    }

    public void invoke(ICallback callback) {
        System.out.println("在调用回调函数之前做一些事情");

        // 调用回调函数
        callback.callbackMethod();

        System.out.println("在调用回调函数之后做一些事情");
    }
}

main函数

import callback.example.CallbackInvoker;
import callback.example.CallbackProvider;

public class Main {
    public static void main(String[] args) {
        // 定义回调函数的提供者与调用者
        CallbackProvider provider = new CallbackProvider();
        CallbackInvoker invoker = new CallbackInvoker();
        //传入实现回调接口的对象
        invoker.invoke(provider);
    }
}

main函数执行结果

在调用回调函数之前做一些事情
我是回调函数,看到此处说明我作为回调函数被调用了
在调用回调函数之后做一些事情

逻辑上来看,这样和前一种实现方式并无多大区别,仍然定义了回调函数的提供类。但是,Java 提供了匿名类这一用法,可以大大简化上述逻辑,无需定义CallbackProvider类,只需在需要回调函数的地方提供一个匿名类即可。

匿名类的方式实现回调方法的定义与传入

import callback.example.CallbackInvoker;
import callback.example.ICallback;

public class Main {
    public static void main(String[] args) {
        CallbackInvoker callbackInvoker = new CallbackInvoker();
        callbackInvoker.invoke(new ICallback() {
            @Override
            public void callbackMethod() {
                System.out.println("匿名类中定义的回调函数逻辑,通过匿名类实现了逻辑的进一步简化,提高了程序的通用性与便捷性");
            }
        });
    }
}

执行结果

在调用回调函数之前做一些事情
匿名类中定义的回调函数逻辑,通过匿名类实现了逻辑的进一步简化,提高了程序的通用性与便捷性
在调用回调函数之后做一些事情

可以看到,通过匿名类配合自定义的回调接口ICallback,我们实现了回调函数的通用化,并将回调函数的调用者与实现者解耦,减少了需要定义的类的数量,大大简化了程序的逻辑。

Java 和 C 语言实现回调的区别

通过上述内容的对比,二者实现回调的区别自然显而易见了。Java 中的回调往往通过传入实现回调函数的类或者通过抽象回调函数为接口,以匿名类的方式实现回调。C 语言的回调就纯粹的多,由于 C 语言函数指针的特性,直接通过传入函数指针即可实现回调。对比下来也可以认为,Java 通过接口+匿名类的方式实现了函数指针的作用,匿名类相当于函数指针的载体,匿名类实现的接口方法即为函数指针所指向的函数。