您好,登錄后才能下訂單哦!
本篇內容介紹了“Java中的回調機制怎么實現”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
在一個應用系統中,無論使用何種語言開發,必然存在模塊之間的調用,調用的方式分為幾種:
(1)同步調用
同步調用是最基本并且最簡單的一種調用方式,類A的方法a()調用類B的方法b(),一直等待b()方法執行完畢,a()方法繼續往下走。這種調用方式適用于方法b()執行時間不長的情況,因為b()方法執行時間一長或者直接阻塞的話,a()方法的余下代碼是無法執行下去的,這樣會造成整個流程的阻塞。
(2)異步調用
異步調用是為了解決同步調用可能出現阻塞,導致整個流程卡住而產生的一種調用方式。類A的方法方法a()通過新起線程的方式調用類B的方法b(),代碼接著直接往下執行,這樣無論方法b()執行時間多久,都不會阻塞住方法a()的執行。
但是這種方式,由于方法a()不等待方法b()的執行完成,在方法a()需要方法b()執行結果的情況下(視具體業務而定,有些業務比如啟異步線程發個微信通知、刷新一個緩存這種就沒必要),必須通過一定的方式對方法b()的執行結果進行監聽。
在Java中,可以使用Future+Callable的方式做到這一點,具體做法可以參見我的這篇文章Java多線程21:多線程下其他組件之CyclicBarrier、Callable、Future和FutureTask。
(3)回調
1、什么是回調?
一般來說,模塊之間都存在一定的調用關系,從調用方式上看,可以分為三類同步調用、異步調用和回調。同步調用是一種阻塞式調用,即在函數A的函數體里通過書寫函數B的函數名來調用之,使內存中對應函數B的代碼得以執行。異步調用是一種類似消息或事件的機制解決了同步阻塞的問題,例如 A通知 B后,他們各走各的路,互不影響,不用像同步調用那樣, A通知 B后,非得等到 B走完后, A才繼續走 。回調是一種雙向的調用模式,也就是說,被調用的接口被調用時也會調用對方的接口,例如A要調用B,B在執行完又要調用A。
2、回調的用途
回調一般用于層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調。例如作為一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。
Java多線程中可以通過callable和future或futuretask結合來獲取線程執行后的返回值。實現方法是通過get方法來調用callable的call方法獲取返回值。
其實這種方法本質上不是回調,回調要求的是任務完成以后被調用者主動回調調用者的接口。而這里是調用者主動使用get方法阻塞獲取返回值。
public class 多線程中的回調 { //這里簡單地使用future和callable實現了線程執行完后 public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newCachedThreadPool(); Future<String> future = executor.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("call"); TimeUnit.SECONDS.sleep(1); return "str"; } }); //手動阻塞調用get通過call方法獲得返回值。 System.out.println(future.get()); //需要手動關閉,不然線程池的線程會繼續執行。 executor.shutdown(); //使用futuretask同時作為線程執行單元和數據請求單元。 FutureTask<Integer> futureTask = new FutureTask(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("dasds"); return new Random().nextInt(); } }); new Thread(futureTask).start(); //阻塞獲取返回值 System.out.println(futureTask.get()); } @Test public void test () { Callable callable = new Callable() { @Override public Object call() throws Exception { return null; } }; FutureTask futureTask = new FutureTask(callable); } }
曾經自己偶爾聽說過回調機制,隱隱約約能夠懂一些意思,但是當讓自己寫一個簡單的示例程序時,自己就傻眼了。隨著工作經驗的增加,自己經常聽到這兒使用了回調,那兒使用了回調,自己是時候好好研究一下Java回調機制了。網上關于Java回調的文章一抓一大把,但是看完總是云里霧里,不知所云,特別是看到抓取別人的代碼走兩步時,總是現眼。于是自己決定寫一篇關于Java機制的文章,以方便大家和自己更深入的學習Java回調機制。
首先,什么是回調函數,引用百度百科的解釋:回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應[2].
不好意思,上述解釋我看了好幾遍,也沒理解其中深刻奧秘,相信一些讀者你也一樣。光說不練假把式,咱們還是以實戰理解脈絡。
本文以底層服務BottomService和上層服務UpperService為示例,利用上層服務調用底層服務,整體執行過程如下:
第一步: 執行UpperService.callBottomService();
第二步: 執行BottomService.bottom();
第三步:執行UpperService.upperTaskAfterCallBottomService()
同步調用時序圖:
同步調用時序圖
1.1.1 底層服務類:BottomService.java
package synchronization.demo; /** * Created by lance on 2017/1/19. */ public class BottomService { public String bottom(String param) { try { // 模擬底層處理耗時,上層服務需要等待 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return param +" BottomService.bottom() execute -->"; } }
1.1.2 上層服務接口: UpperService.java
package synchronization.demo; /** * Created by lance on 2017/1/19. */ public interface UpperService { public void upperTaskAfterCallBottomService(String upperParam); public String callBottomService(final String param); }
1.1.3 上層服務接口實現類:UpperServiceImpl.java
package synchronization.demo; /** * Created by lance on 2017/1/19. */ public class UpperServiceImpl implements UpperService { private BottomService bottomService; @Override public void upperTaskAfterCallBottomService(String upperParam) { System.out.println(upperParam + " upperTaskAfterCallBottomService() execute."); } public UpperServiceImpl(BottomService bottomService) { this.bottomService = bottomService; } @Override public String callBottomService(final String param) { return bottomService.bottom(param + " callBottomService.bottom() execute --> "); } }
1.1.4 Test測試類:Test.java
package synchronization.demo; import java.util.Date; /** * Created by lance on 2017/1/19. */ public class Test { public static void main(String[] args) { BottomService bottomService = new BottomService(); UpperService upperService = new UpperServiceImpl(bottomService); System.out.println("=============== callBottomService start ==================:" + new Date()); String result = upperService.callBottomService("callBottomService start --> "); //upperTaskAfterCallBottomService執行必須等待callBottomService()調用BottomService.bottom()方法返回后才能夠執行 upperService.upperTaskAfterCallBottomService(result); System.out.println("=============== callBottomService end ====================:" + new Date()); } }
1.1.5 輸出結果:
=============== callBottomService start ==================:Thu Jan 19 14:59:58 CST 2017 callBottomService start --> callBottomService.bottom() execute --> BottomService.bottom() execute --> upperTaskAfterCallBottomService() execute. =============== callBottomService end ====================:Thu Jan 19 15:00:01 CST 2017
注意輸出結果:
是同步方式,Test調用callBottomService()等待執行結束,然后再執行下一步,即執行結束。callBottomService開始執行時間為Thu Jan 19 14:59:58 CST 2017,執行結束時間為Thu Jan 19 15:00:01 CST 2017,耗時3秒鐘,與模擬的耗時時間一致,即3000毫秒。
前幾天公司面試有問道java回調的問題,因為這方面也沒有太多研究,所以回答的含糊不清,這回特意來補習一下。看了看網上的回調解釋和例子,都那么的繞口,得看半天才能繞回來,其實吧,回調是個很簡單的機制。在這里我用簡單的語言先來解釋一下:假設有兩個類,分別是A和B,在A中有一個方法a(),B中有一個方法b();在A里面調用B中的方法b(),而方法b()中調用了方法a(),這樣子就同時實現了b()和a()兩個方法的功能。
疑惑:為啥這么麻煩,我直接在類A中的B.b()方法下調用a()方法就行了唄。
解答:回調更像是一個約定,就是如果我調用了b()方法,那么就必須要回調,而不需要顯示調用
一、Java的回調-淺
我們用例子來解釋:小明和小李相約一起去吃早飯,但是小李起的有點晚要先洗漱,等小李洗漱完成后,通知小明再一起去吃飯。小明就是類A,小李就是類B。一起去吃飯這個事件就是方法a(),小李去洗漱就是方法b()。
public class XiaoMing { //小明和小李一起吃飯 public void eatFood() { XiaoLi xl = new XiaoLi(); //A調用B的方法 xl.washFace(); } public void eat() { System.out.print("小明和小李一起去吃大龍蝦"); } } 那么怎么讓小李洗漱完后在通知小明一起去吃飯呢 public class XiaoMing { //小明和小李一起吃飯 public void eatFood() { XiaoLi xl = new XiaoLi(); //A調用B的方法 xl.washFace(); eat(); } public void eat() { System.out.print("小明和小李一起去吃大龍蝦"); } }
不過上面已經說過了這個不是回調函數,所以不能這樣子,正確的方式如下
public class XiaoLi{//小李 public void washFace() { System.out.print("小李要洗漱"); XiaoMing xm = new XiaoMing(); //B調用A的方法 xm.eat();//洗漱完后,一起去吃飯 } }
這樣子就可以實現washFace()同時也能實現eat()。小李洗漱完后,再通知小明一起去吃飯,這就是回調。
二、Java的回調-中
可是細心的伙伴可能會發現,小李的代碼完全寫死了,這樣子的場合可能適用和小明一起去吃飯,可是假如小李洗漱完不吃飯了,想和小王上網去,這樣子就不適用了。其實上面是偽代碼,僅僅是幫助大家理解的,真正情況下是需要利用接口來設置回調的。現在我們繼續用小明和小李去吃飯的例子來講講接口是如何使用的。
小明和小李相約一起去吃早飯,但是小李起的有點晚要先洗漱,等小李洗漱完成后,通知小明再一起去吃飯。小明就是類A,小李就是類B。不同的是我們新建一個吃飯的接口EatRice,接口中有個抽象方法eat()。在小明中調用這個接口,并實現eat();小李聲明這個接口對象,并且調用這個接口的抽象方法。這里可能有點繞口,不過沒關系,看看例子就很清楚了。
EatRice接口:
public interface EatRice { public void eat(String food); } 小明: public class XiaoMing implements EatRice{//小明 //小明和小李一起吃飯 public void eatFood() { XiaoLi xl = new XiaoLi(); //A調用B的方法 xl.washFace("大龍蝦", this);//this指的是小明這個類實現的EatRice接口 } @Override public void eat(String food) { // TODO Auto-generated method stub System.out.println("小明和小李一起去吃" + food); } } 小李: public class XiaoLi{//小李 public void washFace(String food,EatRice er) { System.out.println("小李要洗漱"); //B調用了A的方法 er.eat(food); } } 測試Demo: public class demo { public static void main(String args[]) { XiaoMing xm = new XiaoMing(); xm.eatFood(); } }
測試結果:
這樣子就通過接口的形式實現了軟編碼。通過接口的形式我可以實現小李洗漱完后,和小王一起去上網。代碼如下
public class XiaoWang implements EatRice{//小王 //小王和小李一起去上網 public void eatFood() { XiaoLi xl = new XiaoLi(); //A調用B的方法 xl.washFace("輕舞飛揚上網", this); } @Override public void eat(String bar) { // TODO Auto-generated method stub System.out.println("小王和小李一起去" + bar); } }
數學老師讓Tom做一道題,并且Tom做題期間數學老師不用盯著Tom,而是在玩手機,等Tom把題目做完后再把答案告訴老師。
1 數學老師需要Tom的一個引用,然后才能將題目發給Tom。
2 數學老師需要提供一個方法以便Tom做完題目以后能夠將答案告訴他。
3 Tom需要數學老師的一個引用,以便Tom把答案給這位老師,而不是隔壁的體育老師。
回調接口,可以理解為老師接口
//回調指的是A調用B來做一件事,B做完以后將結果告訴給A,這期間A可以做別的事情。 //這個接口中有一個方法,意為B做完題目后告訴A時使用的方法。 //所以我們必須提供這個接口以便讓B來回調。 //回調接口, public interface CallBack { void tellAnswer(int res); }
數學老師類
//老師類實例化回調接口,即學生寫完題目之后通過老師的提供的方法進行回調。 //那么學生如何調用到老師的方法呢,只要在學生類的方法中傳入老師的引用即可。 //而老師需要指定學生答題,所以也要傳入學生的實例。 public class Teacher implements CallBack{ private Student student; Teacher(Student student) { this.student = student; } void askProblem (Student student, Teacher teacher) { //main方法是主線程運行,為了實現異步回調,這里開啟一個線程來操作 new Thread(new Runnable() { @Override public void run() { student.resolveProblem(teacher); } }).start(); //老師讓學生做題以后,等待學生回答的這段時間,可以做別的事,比如玩手機.\ //而不需要同步等待,這就是回調的好處。 //當然你可以說開啟一個線程讓學生做題就行了,但是這樣無法讓學生通知老師。 //需要另外的機制去實現通知過程。 // 當然,多線程中的future和callable也可以實現數據獲取的功能。 for (int i = 1;i < 4;i ++) { System.out.println("等學生回答問題的時候老師玩了 " + i + "秒的手機"); } } @Override public void tellAnswer(int res) { System.out.println("the answer is " + res); } }
學生接口
//學生的接口,解決問題的方法中要傳入老師的引用,否則無法完成對具體實例的回調。 //寫為接口的好處就是,很多個學生都可以實現這個接口,并且老師在提問題時可以通過 //傳入List<Student>來聚合學生,十分方便。 public interface Student { void resolveProblem (Teacher teacher); }
學生Tom
public class Tom implements Student{ @Override public void resolveProblem(Teacher teacher) { try { //學生思考了3秒后得到了答案,通過老師提供的回調方法告訴老師。 Thread.sleep(3000); System.out.println("work out"); teacher.tellAnswer(111); } catch (InterruptedException e) { e.printStackTrace(); } }
測試類
public class Test { public static void main(String[] args) { //測試 Student tom = new Tom(); Teacher lee = new Teacher(tom); lee.askProblem(tom, lee); //結果 // 等學生回答問題的時候老師玩了 1秒的手機 // 等學生回答問題的時候老師玩了 2秒的手機 // 等學生回答問題的時候老師玩了 3秒的手機 // work out // the answer is 111 } }
“Java中的回調機制怎么實現”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。