两个经典例子,让你全面了解Java的回调机制转载请联系程序新视界公众号。前言我们先通过生活中的一个场景来还原回调场景:你遇到了一个技术问题(比如1+1等于多少?太难了!),于是你去请教大牛,大牛说现在很忙,稍后我会告诉你结果。这时候你可能会去朋友圈,等大牛做完作业,他会告诉你答案是2。然后,在这个过程中,提出问题(调用对方的接口),问题之后再告诉你已解决(对方处理后会打电话给您,并通知您处理结果)。这个过程就是回调。系统调用的分类应用系统模块之间的调用通常分为:同步调用、异步调用和回调。同步调用同步调用是最基本的调用方式。A类的a()方法调用了B类的b()方法,A类的方法需要等到B类的方法执行完成后才能继续。如果长时间阻塞B的方法,会导致A类的方法无法正常执行。异步调用如果A调用B,B的执行时间比较长,那么就需要考虑异步处理,让B的执行不影响A。通常在A中启动一个新的线程来调用B,然后A中的代码继续执行。异步通常有两种情况:第一种不需要调用结果,直接调用即可,比如发送消息通知;其次,需要异步调用结果,Java中可以使用Future+Callable来实现。Callback通过上图我们可以看出return是一个双向的调用方法。回调的基本思想是:A调用B,B处理完后调用A提供的回调方法(通常是callbakc())通知结果。回调通常分为:同步回调和异步回调。网络上的大多数回调案例都是同步回调。同步回调类似于同步调用。当代码运行到某个位置,如果遇到需要回调的代码,就会在这里等待,等待回调结果返回后再继续执行。异步回调类似于异步调用。当代码执行到需要回调的代码时,不会停止,而是继续执行。当然,回调的结果可能会在一段时间后返回。同步回调示例下面以同步回调为例,讲解一下回调的Java代码实现。整个过程模拟了上面问答题的场景。首先,定义一个CallBack接口来分离回调函数:publicinterfaceCallBack{voidcallback(Stringstring);}CallBack接口提供回调方法,用于回调时调用。然后定义提问者:publicclassPersonimplementsCallBack{privateGeniusgenius;publicPerson(Geniusgenius){this.genius=genius;}@Overridepublicvoidcallback(Stringstring){System.out.println("Receivedanswer:"+string);}publicvoidask(){genius.answer(this);}}由于Person需要提供回调方法,因此它实现了CallBack接口及其方法。这些方法主要处理回调结果。同时,由于Person需要调用Genius对应的方法,所以需要持有Genius的引用,通过构造方法传入。定义回答问题的Genius类:publicclassGenius{publicvoidanswer(CallBackcallBack){System.out.println("我在忙其他事情...");try{Thread.sleep(2000);System.out.println("忙完其他事情,开始计算...");}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("天才算出答案:2");//回调告诉你callBack.callback("2");}}模拟master忙,线程休眠2秒。忙碌之后,它开始帮助计算答案。得到应答后调用CallBack接口的回调方法回调并通知结果。通过Main方法测试:publicstaticvoidmain(String[]args){Geniusgenius=newGenius();Personyou=newPerson(genius);you.ask();}执行打印结果如下:busywithotherthings...finished其他的,开始计算...天才算出答案:2得到答案:2上面的过程实现了一个同步回调函数。当然,从编程的角度,Person和Genius还可以进一步抽象,以接口的形式呈现。在上述回调机制的代码实现中,核心是在调用answer方法时传递了this参数,也就是调用者自己。从本质上讲,回调是一种思想,一种机制。至于如何实现,如何通过代码优雅且可扩展性高的实现回调,就看八仙大神各显神通了。异步回调示例上面的示例演示了一个同步回调。显然,调用受Genius执行时间的影响。需要等到Genius处理完,才能继续执行Person方法中的后续代码。下面是对上面例子的改进。Person提供了一个支持异步回调的方法:publicvoidaskASyn(){System.out.println("新建一个线程提问");newThread(()->genius.answer(this))。start();System.out.println("Thenewthreadhasstarted...");}在这个方法中,创建了一个新的线程来处理Genius#answer方法的调用,使得Genius#answer方法可以跳过block,直接进行下面的操作(日志打印)。在main方法中,将调用方法改为askASyn,打印结果如下:新建线程提问。新线程开始了...忙其他事情...做完其他事情,开始计算...天才算出答案为:2收到的答案:2可见“新线程已开始。.."直接打印出来,然后打印出Genius#answer方法中处理日志和回调时回调方法接收到的信息。Future-basedsemi-asynchronousprocessing除了上述的同步和异步处理之外,还有一种介于同步和异步之间的Future-based半异步处理。在Java中使用nio之后,我们并不能马上得到真正的数据,而是先得到一个“未来”,可以理解为邮戳或者快递单。为了知道真实的数据,我们需要通过快递单号“future”不断查看快递是否派送。真的交付了。期货是一种抽象,表示将在某个时候可用的价值。Future要么获取计算结果,要么在计算失败后获取异常。Future一般在什么时候使用?一般来说,在执行一个耗时任务时,使用Future可以让线程暂时去处理其他任务,待长任务完成后再返回结果。经常使用Future的场景有:1.计算密集型场景。2.处理大量数据。3、远程方法调用等Java在java.util.concurrent包中自带Future接口,使用Executor异步执行。比如下面的代码,每次给ExecutorService.submit()方法传递一个Runnable对象,都会得到一个回调Future,用来检测是否执行。该方法可用于同步等待线路处理结果完成。publicclassTestFuture{publicstaticvoidmain(String[]args){//实现一个Callable接口Callable
