模塊之間總是存在這一定的接口,從調用方式上看,可以分為三類:同步調用、回調和異步調用。下面著重詳解回調機制。
1. 概述
Java 中的回調機制是一個比較常見的機制,只是有可能在你的程序中使用得比較少,在一些大型的框架中回調機制隨處可見。本文就通過一些具體的實例,慢慢走近Java 的回調機制。
2.回調
所謂回調:就是A類中調用B類中的某個方法C,然後B類中反過來調用A類中的方法D,D這個方法就叫回調方法。實際在使用的時候,也會有不同的回調形式,比如下面的這幾種。
2.1 同步回調
這裡我假設這樣的一種情況。
A 公司的總監B 跟他的下屬(項目經理C)說要做一個調研,不過不用C 自己親力親為。可以讓經理C 去安排他下面的程序員D 去完成。經理C 找到了程序員D,並告訴他,現在要完成一個調研任務。並且把調研的結果告訴經理C。如果有問題,還是要繼續的。 因為這裡是C 讓D 去做一件事情,之後D 還是要將結果與C 進行溝通。這樣就是回調的模型了。下面是一般回調的類圖:
首先我們要有一個回調的接口CallbackInterface
CallbackInterface.java
public interface CallbackInterface { public boolean check(int result);}背景裡,程序員D 是要將結果與項目經理C 進行溝通的,所以這裡項目經理需要實現上面的回調接口:
Manager.java
public class Manager implements CallbackInterface { private Programmer programmer = null; public Manager(Programmer _programmer) { this.programmer = _programmer; } /** * 用於Boss 下達的委託*/ public void entrust() { arrange(); } // 進行安排下屬進行study 工作private void arrange() { System.out.println("Manager 正在為Programmer 安排工作"); programmer.study(Manager.this); System.out.println("為Programmer 安排工作已經完成,Manager 做其他的事情去了。"); } @Override public boolean check(int result) { if (result == 5) { return true; } return false; }}對於程序員D 來說他需要持有一個經理C 的引用,以便與他溝通。不過,這裡是總監B 讓經理C 去安排的任務。也就是說這裡也可以讓其他的經理,比如說經理B1, B2等等。因為經理都實現了回調的接口,所以這裡就可以直接讓程序員D 持有這個接口就可以了。如下:
Programmer.java
public class Programmer { public void study(CallbackInterface callback) { int result = 0; do { result++; System.out.println("第" + result + " 次研究的結果"); } while (!callback.check(result)); System.out.println("調研任務結束"); }}對於總監來說就更簡單明了了,因為這相當於一個Client 測試:
Boss.java
public class Boss { public static void main(String[] args) { Manager manager = new Manager(new Programmer()); manager.entrust(); }}運行結果:
Manager 正在為Programmer 安排工作第1 次研究的結果第2 次研究的結果第3 次研究的結果第4 次研究的結果第5 次研究的結果調研任務結束為Programmer 安排工作已經完成,Manager 做其他的事情去了。
2.2 異步回調
還是上面的例子,你的項目經理不可能要一直等你調研的結果。而是把這個任務交給你之後,他就不管了,他做他的,你做你的。所以,這裡需要對回調的函數進行異步處理。
所以,這裡我們需要修改Programmer 類的代碼,修改如下:
Programmer.java
public class Programmer { public Programmer() { } public void study(CallbackInterface callback) { new StudyThread(callback).start(); } // --------------------------- Programmer 正在做的工作--------------------------- class StudyThread extends Thread { CallbackInterface callback = null; public StudyThread(CallbackInterface _callback) { callback = _callback; } @Override public void run() { int result = 0; do { result++; System.out.println("第" + result + " 次研究的結果"); } while (!callback.check(result)); System.out.println("調研任務結束"); } }}運行結果:
Manager 正在為Programmer 安排工作為Programmer 安排工作已經完成,Manager 做其他的事情去了。
第1 次研究的結果第2 次研究的結果第3 次研究的結果第4 次研究的結果第5 次研究的結果調研任務結束
2.3 閉包與回調
閉包(closure)是一個可調用的對象,它記錄了一些信息,這些信息來自於創建它的作用域。
2.3.1 普通調用
首先,我們可以看看在正常情況下的調用是怎麼進行的。
Incrementable.java
interface Incrementable { void increment();}這是一個普通的接口(在普通調用裡只是普通接口,在回調中就是回調接口,這一點應該很好理解吧)。
Callee1.java
class Callee1 implements Incrementable { private int i = 0; @Override public void increment() { i++; System.out.println(i); }}Callbacks.java
public class Callbacks { public static void main(String[] args) { Callee1 callee1 = new Callee1(); callee1.increment(); }}Callbacks 是一個測試客戶端類,沒啥好說的,直接看上面的代碼。
2.3.2 回調初試
上面的普通調用也沒啥好說的,因為這對於一個正常的Java 程序員來說都應該是想都不用想就可以搞定的事情。
現在如果要構成回調,那麼對於程序的結構或是邏輯的思維上都不可能只有一個被調用者(被回調的對象Callee1),還需要一個調用者對象。調用者可以像下面這樣來編寫:
Caller.java
class Caller { private Incrementable callbackReference; public Caller(Incrementable _callbackReference) { callbackReference = _callbackReference; } void go() { callbackReference.increment(); }}這裡Caller 持有一個回調接口的引用callbackReference,就像在上面說到的程序員需要持有一個項目經理的引用,這樣就可以通過這個引用來與項目經理溝通。這裡的callbackReference 也正是起到了這個作用。
現在我們來看看測試類的編寫:
Callbacks.java
public class Callbacks { public static void main(String[] args) { Callee1 callee1 = new Callee1(); Caller caller1 = new Caller(callee1); caller1.go(); }}對於到目前為止的程序代碼,完全可以對比上面項目經理安排程序員調研技術難題的代碼。有異曲同工之妙。
2.3.3 閉包回調
相比於正常的回調,閉包回調的核心自然是在於閉包,也就是對作用域的控制。
現在假設有一個用戶(其他程序員)自定義了一個MyInCrement 類,同時包含了一個increment 的方法。如下:
class MyInCrement { public void increment() { System.out.println("MyCrement.increment"); } static void f(MyInCrement increment) { increment.increment(); }}另外有一個類Callee2 繼承自上面這個類:
class Callee2 extends MyInCrement { private int i = 0; public void increment() { super.increment(); i++; System.out.println(i); }}顯而易見這裡如果要調用increment() 方法,就變成了一般的函數調用了。所以這裡我們需要修改上面的Callee2 類,修改的目標就是讓Callee2 類可以兼容MyInCrement 類的increment() 方法和Incrementable 的increment() 方法。修改後:
class Callee2 extends MyInCrement { private int i = 0; public void increment() { super.increment(); i++; System.out.println(i); } private class Closure implements Incrementable { @Override public void increment() { Callee2.this.increment(); } } Incrementable getCallbackReference() { return new Closure(); }}注意,這裡的Closure 類是一個私有的類,這是一個閉包的要素。因為Closure 類是私有的,那麼就要有一個對外開放的接口,用來對Closure 對象的操作,這裡就是上面的getCallbackReference() 方法。 Caller 類則沒有改變。
對於測試客戶端就直接看代碼吧:
public class Callbacks { public static void main(String[] args) { Callee2 callee2 = new Callee2(); Caller caller2 = new Caller(callee2.getCallbackReference()); caller2.go(); }}以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。