Java 是如何利用接口避免函数回调的方法

一、引言

在许多编程语言中,都有函数回调这一概念。C 和 C++ 中有函数指针,因此可以将函数作为参数传给其它函数,以便过后调用。而在 JavaScript 中,更是将函数回调发挥到了极致,各种事件的处理,特别是异步事件,基本都靠函数回调来完成。

在 Java 中,同样可以实现函数回调。虽然没有函数指针,但 Java 可以通过反射机制来获得一个类的方法,将其以 java.lang.reflect.Method 类型参数传递给其它函数,然后通过 Method 对象的 invoke 方法来调用该函数。

尽管如此,这种方式的调用步骤相对繁琐、执行效率低、难以调试。在 Java 中,有比函数回调更加优雅的机制,那就是接口。

二、为什么需要函数回调

函数回调,实际上是延迟实现某些功能的一种方式。

如果我们事先知道程序应该执行哪些操作,那么完全不需要函数回调,直接在编程时实现即可。

但很多时候,在编写代码时,特别是写工具类、功能库或框架时,实现的是相对通用和抽象的功能,而具体场景下的功能则由使用这些类的开发者来实现。

函数回调,可以解决这种事先不知道具体实现的情况。

排序函数的例子

举例来说,当我们要实现一个通用的排序函数时,事先并不知道其他开发者会用该函数来对哪些类型的元素进行排序,也就不知道以何种标准来判断这些元素的偏序(大小)关系。

因此,可以要求其他开发者在使用排序函数时,必须提供一个比较函数 compare,这样我们就可以用 compare 比较待排序元素的大小,而无需事先知道元素是什么类型,也无需知道 compare 的具体实现。

这里 compare 函数对于排序函数来说,就是回调函数。

伪代码表示如下:

//通用的排序函数
void sort(Object[] array, Method compare) {
  //利用 compare 函数比较 array 中元素的大小关系
  //以便对 array 进行排序
}

//由调用者实现具体的比较函数
int compare(Object a, Object b) {
  //比较元素a、b,并返回大小关系
}

异步处理函数的例子

再比如说,当我们编写一个异步处理函数时,事先不知道其他开发者在处理完成时要进行哪些操作,因为这些操作只有在特定场景下使用该函数时才能知道。

于是可以要求开发者在使用该函数时,提供一个回调函数 callback。这样我们在编写异步处理函数时,就可以调用 callback 函数来进行一些收尾的工作,而无需事先知道这些收尾的工作是什么。

伪代码表示如下:

//异步处理函数
void asynProcess(Method callback) {
  //执行异步任务
  callback();
}

//由调用者实现具体的回调函数
void callback() {
  //异步处理完成后要进行的操作
}

三、用接口代替函数回调

上面我们提到,之所以使用函数回调这一方式,是因为 事先不知道某些功能的具体实现,因此将具体实现留给其他开发者完成。

有没有觉得这句话仿佛在描述 Java 的接口?接口(interface)是一组方法的抽象定义,具体实现由实现该接口的类来完成。

所以,利用面向对象和接口这两个特性,可以代替函数回调。

我们以上面举的两个例子来说明接口是如何代替函数回调的。

排序函数

用接口实现排序函数,不再要求开发者在使用该排序函数时提供回调函数 compare,而是要求开发者确保待排序元素实现了 Comparable 接口,基于“待排序元素已经实现了 Comparable 接口“这一前提下,我们无需知道待排序元素的类型,就可以实现排序功能。

//通用的排序函数
void sort(Object[] array) {
  //利用 Comparable 接口的 compareTo 方法
  //比较元素的大小,以便对 array 进行排序。
}

//由排序函数定义的接口
public interface Comparable {
  public int compareTo(Object other);
}

//由调用者实现 Comparable 接口
public class Element implements Comparable {
  @Override
  public int compareTo(Object other) {
    //判断当前 Element 与 other 的大小关系
    //并返回两者的关系
  }
}

异步处理函数

使用接口来实现异步处理函数时,不要求开发者提供回调函数 callback,而是要求提供一个实现了指定接口的对象,这很好地体现了 Java 面向对象的思想。相比提供一个函数,一个对象包含的信息更丰富,使用起来更加灵活。但本质上,该异步处理函数还是利用接口来完成收尾工作的。

//异步处理函数
void asynProcess(ActionListener al) {
  //执行异步任务
  al.actionPerformed();
}

//由异步处理函数定义的接口
public interface ActionListener {
  void actionPerformed();
}

//由调用者实现 ActionListener 接口
public class ExtraTask implements ActionListener {
  @Override
  public void actionPerformed() {
    //异步处理函数执行完成时,需要进行的额外工作
  }
}

//调用异步处理函数
public static void main(String[] args) {
  asynProcess(new ExtraTask());
}

四、总结

回调方式可以总结为:实现一个通用函数 func,在具体场景中调用这个通用函数时,调用者需要提供合适的回调函数 callback。通用函数 func 利用该回调函数,完成具体场景中的任务。

而接口实现的方式则是:实现一个通用函数 func,在具体场景中调用这个通用函数时,被操作的对象需要自己实现合适的接口,通用函数会利用该接口,完成具体场景中的任务。

利用函数回调或者接口,都可以解决事先不知道具体实现的情况。函数回调方式传递的是函数,而接口方式传递的是实现了该接口的对象。

在 Java 中,函数回调需要利用反射机制来完成,易出错、效率低,而使用接口可以让代码的逻辑更加清晰、运行效率更高、也更便于调试。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Java回调函数与观察者模式实例代码
  • Java回调函数实例代码详解
  • Java通过匿名类来实现回调函数实例总结
  • 基于Java 谈回调函数
  • Java 回调函数详解及使用
  • Java 回调函数深入理解
  • 再谈java回调函数
(0)

相关推荐

  • 基于Java 谈回调函数

    杂谈 还是习惯,写点什么之前,都喜欢来点杂谈. 最近都在搞微信的开发,终于在昨晚进入测试阶段了,好快就测出 bug 来,但也好快修复了.这也让我更担心. 虽说是进入内测阶段,但那代码的结构也真太难看了,还得好好重构一下.迟点得写写关于这微信公众号的开发文章.下次再详谈. 今天开始了三天半的小长假,但我还是坚守在学校.有点不知道为了什么. 刚给 wp 装了个 markdown 插件,爽歪歪!! 在写这篇文章的时候,还是用着 wordpress 的,但在发表的时候,已经换成 hexo 了.原因:没有

  • Java通过匿名类来实现回调函数实例总结

    在C语言中,函数名可以当做函数指针传递给形参从而实现回调 void f1() { printf("f1()\n"); } void f2() { printf("f2()\n"); } void f3() { printf("f3()\n"); } void do_func(void(*f)()) { f(); } int main() { do_func(f1); do_func(f2); do_func(f3); } 在C++11中,实现回调

  • Java回调函数实例代码详解

    首先说说什么叫回调函数? 在WINDOWS中,程序员想让系统DLL调用自己编写的一个方法,于是利用DLL当中回调函数(CALLBACK)的接口来编写程序,使它调用,这个就 称为回调.在调用接口时,需要严格的按照定义的参数和方法调用,并且需要处理函数的异步,否则会导致程序的崩溃. 这样的解释似乎还是比较难懂,这里举个简 单的例子: 程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序.程序员B要让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法.目的达到

  • 再谈java回调函数

    又遇到了回调函数,这次打算写下来分享一下. 所谓回调函数,或者在面向对象语言里叫回调方法,简单点讲,就是回头在某个时间(事件发生)被调用的函数. 再详细点:就是一个函数A,作为参数,传入了另一个函数B,然后被B在某个时间调用. 这里可以有疑问了,既然是一个函数调用另一个函数,可以在函数体里面调用啊,为什么还要把函数作为参数传到另一个函数里被调用?何况还有一些语言(比如java)不支持把函数作为参数. 对的,确实可以在函数体里调用另一个函数,功能上好像是没差别的,但是这里有一个问题,就是你要调用的

  • Java 回调函数详解及使用

    Java 回调函数详解 前言: C语言中回调函数解释: 回调函数(Callback Function)是怎样一种函数呢? 函数是用来被调用的,我们调用函数的方法有两种: 直接调用:在函数A的函数体里通过书写函数B的函数名来调用之,使内存中对应函数B的代码得以执行.这里,A称为"主叫函数"(Caller),B称为"被叫函数"(Callee). 间接调用:在函数A的函数体里并不出现函数B的函数名,而是使用指向函数B的函数指针p来使内存中属于函数B的代码片断得以执行--听

  • Java 回调函数深入理解

     Java 回调函数 概要: 所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数.例如Win32下的窗口过程函数就是一个典型的回调函数.一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供.由于S并不知道C提供的B姓甚名谁,所以S会约定B的接口规范(函数原型),然后由C提前通过S的一个函数R告诉S自己将要使用B函数,这个过程称为回调函数的注册,R称为注册函数.Web Service以及Ja

  • Java回调函数与观察者模式实例代码

    本文研究的主要是Java回调函数与观察者模式的实现,具体介绍和实现代码如下. 观察者模式(有时又被称为发布(publish )-订阅(Subscribe)模式.模型-视图(View)模式.源-收听者(Listener)模式或从属者模式)是软件设计模式的一种.在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知.这通常透过呼叫各观察者所提供的方法来实现.此种模式通常被用来实现事件处理系统. 什么时候使用观察者模式: 当一个抽象模型有两个方面, 其中一个方面依

  • Java 是如何利用接口避免函数回调的方法

    一.引言 在许多编程语言中,都有函数回调这一概念.C 和 C++ 中有函数指针,因此可以将函数作为参数传给其它函数,以便过后调用.而在 JavaScript 中,更是将函数回调发挥到了极致,各种事件的处理,特别是异步事件,基本都靠函数回调来完成. 在 Java 中,同样可以实现函数回调.虽然没有函数指针,但 Java 可以通过反射机制来获得一个类的方法,将其以 java.lang.reflect.Method 类型参数传递给其它函数,然后通过 Method 对象的 invoke 方法来调用该函数

  • Kotlin中let()with()run()apply()also()函数的使用方法与区别

    相比Java, Kotlin提供了不少高级语法特性.对于一个Kotlin的初学者来说经常会写出一些不够优雅的代码.在Kotlin中的源码标准库(Standard.kt)中提供了一些Kotlin扩展的内置函数可以优化kotlin的编码.Standard.kt是Kotlin库的一部分,它定义了一些基本函数. 这个源代码文件虽然一共不到50行代码,但是这些函数功能都非常强大. 一.回调函数的Kotin的lambda的简化 在Kotlin中对Java中的一些的接口的回调做了一些优化,可以使用一个lamb

  • 浅谈Java 继承接口同名函数问题

    在Java中如果一个类同时继承接口A与B,并且这两个接口中具有同名方法,会怎么样? 动手做实验: interface A{ void fun(); } interface B{ void fun(); } interface C extends A,B{ } public class Test implements C{ @Override public void fun() { System.out.println("hehe"); } public static void main

  • Java Predicate及Consumer接口函数代码实现解析

    Predicate函数编程 Predicate功能判断输入的对象是否符合某个条件.官方文档解释到:Determines if the input object matches some criteria. 了解Predicate接口作用后,在学习Predicate函数编程前,先看一下Java 8关于Predicate的源码: @FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate o

  • Java线程的start方法回调run方法的操作技巧

    面试中可能会被问到为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法? Java 创建线程的方法 实际上,创建线程最重要的是提供线程函数(回调函数),该函数作为新创建线程的入口函数,实现自己想要的功能.Java 提供了两种方法来创建一个线程: 继承 Thread 类 class MyThread extends Thread{ public void run() { System.out.println("My thread is started.&qu

  • kotlin 定义接口并实现回调的例子

    开发环境 android studio 3.0.1 已支持 kotlin 1.定义接口 interface CallBack{ fun callBack(info : String) } 2.实现接口 : 为继承\实现 class CallBacks : CallBack{ override fun callBack(info : String){ Log.d("MainApp","current info $info") } } 或者 private var mC

  • java高级用法之JNA中的回调问题

    目录 简介 JNA中的Callback callback的应用 callback的定义 callback的获取和应用 在多线程环境中使用callback 总结 简介 什么是callback呢?简单点说callback就是回调通知,当我们需要在某个方法完成之后,或者某个事件触发之后,来通知进行某些特定的任务就需要用到callback了. 最有可能看到callback的语言就是javascript了,基本上在javascript中,callback无处不在.为了解决callback导致的回调地狱的问

  • 详解Go语言如何利用高阶函数写出优雅的代码

    目录 前言 问题 白银 黄金 王者 总结 前言 go项目中经常需要查询db,按照以前java开发经验,会根据查询条件写很多方法,如: GetUserByUserID GetUsersByName GetUsersByAge 每一种查询条件写一个方法,这种方式对外是挺好的,对外遵循严格原则,让每个对外的方法接口是明确的.但是对内的话,应该尽可能的通用,做到代码复用,少写代码,让代码看起来更优雅.整洁. 问题 在review代码的时候,针对上面3个方法,一般写法是 func GetUserByUse

  • javascript中利用柯里化函数实现bind方法

    柯理化函数思想:一个js预先处理的思想:利用函数执行可以形成一个不销毁的作用域的原理,把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预先存储的值进行相关的操作处理即可: 柯里化函数主要起到预处理的作用: bind方法的作用:把传递进来的callback回调方法中的this预先处理为上下文context; /** * bind方法实现原理1 * @param callback [Function] 回调函数 * @param con

  • Java多态和实现接口的类的对象赋值给接口引用的方法(推荐)

    接口的灵活性就在于"规定一个类必须做什么,而不管你如何做". 我们可以定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上述的超类对象引用访问子类对象的机制相似. //定义接口InterA interface InterA { void fun(); } //实现接口InterA的类B class B implements InterA { public void fun() { System.out.pri

随机推荐