前言java
在Java社區的各類開源工具中,回調方法的使用俯拾便是。因此熟悉回調方法無疑能加速本身對開源輪子的掌握。
網上搜了一些文章,奈何對回調方法的介紹大多隻停留在什麼是回調方法的程度上。本篇文章嘗試從回調方法怎麼來的、爲何要使用回調方法以及在實際項目中如何使用等方面來介紹下。安全
場景異步
場景選擇的得當與否,很影響讀者的繼續閱讀的興趣甚至理解的主動性(長期做爲互聯網技術博文讀者的我,深有感觸)。
好場景私覺得是:熟悉且簡單。ide
本例當心翼翼選擇的場景是:寫做業。(hope you like)工具
本身寫this
注:寫做業這個動做至少交代三個方面:誰,什麼動做(寫),寫什麼。
下面先從(有個學生,寫,做業)開始。線程
# 1. 有個學生 Student student = new Student(); # 2. 該學生有寫做業這個動做須要執行 student.doHomeWork(someHomeWork); # 3. 注意到這個寫做業這個動做是須要獲得入參「做業」的後才能進行的。因此給這個學生new了個簡單的題目作。 String aHomeWork = "1+1=?"; student.doHomeWork(aHomeWork);
至此,完成寫做業的動做。code
完整代碼對象
public class Student { public void doHomeWork(String homeWork) { System.out.println("做業本"); if("1+1=?".equals(homeWork)) { System.out.println("做業:"+homeWork+" 答案:"+"2"); } else { System.out.println("做業:"+homeWork+" 答案:"+"不知道~~"); } } public static void main(String[] args) { Student student = new Student(); String aHomeWork = "1+1=?"; student.doHomeWork(aHomeWork); } }
程序執行繼承
做業本 做業:1+1=? 答案:2
咱們必定要把焦點聚焦到,」寫做業「這個需求上面。
該學生寫做業的方法是現成的,可是須要有做業做爲入參,怎麼獲取做業纔是完成動做的關鍵。但願這點能深深印入咱們的腦海。
讓室友幫忙解答
上面的例子中該同窗本身調用本身的方法,把收到的homework直接寫了。
可是現實可能會出現各類各樣的問題致使該同窗不能(xiang)本身來作。好比他想玩遊戲或者有約會。因此他拜託了他的好室友(roommate)來幫忙寫下。該怎麼實現呢。
#1. 由於室友幫忙寫,因此在doHomeWork動做裏面,就不須要有邏輯判斷的代碼,由於舍友會直接把答案寫進來。改爲: student.doHomeWork(aHomeWork, theAnswer); #上句中作做業的動做支持傳入「做業」和「答案」,有了這兩個,就說明能作好做業了。 #其中aHomeWork做業是已知的,可是theAnswer這個答案倒是室友提供的。 #室友怎麼才能提供答案呢,最簡單是,室友這個對象直接提供一個傳入做業,傳出答案的方法,這樣該同窗就能夠直接調用了。 RoomMate roomMate = new RoomMate(); String theAnswer = roomMate.getAnswer(aHomeWork); student.doHomeWork(aHomeWork, theAnswer);
完整代碼
public class Student { public void doHomeWork(String homeWork, String answer) { System.out.println("做業本"); if(answer != null) { System.out.println("做業:"+homeWork+" 答案:"+ answer); } else { System.out.println("做業:"+homeWork+" 答案:"+ "(空白)"); } } public static void main(String[] args) { Student student = new Student(); String aHomeWork = "1+1=?"; RoomMate roomMate = new RoomMate(); String theAnswer = roomMate.getAnswer(aHomeWork); student.doHomeWork(aHomeWork, theAnswer); } }
public class RoomMate { public String getAnswer(String homework) { if("1+1=?".equals(homework)) { return "2"; } else { return null; } } }
程序執行
做業本 做業:1+1=? 答案:2
怒,說好的回調方法呢~~
由於到目前爲止,不須要使用回調方法。
技術老是伴隨新的需求出現的。
好,給你新的需求。
注意重點來了
咱們回顧下這兩行代碼
#室友寫好做業 String theAnswer = roomMate.getAnswer(aHomeWork); #該同窗直接抄答案,完成做業 student.doHomeWork(aHomeWork, theAnswer);
該同窗想了想,你給了答案有屁用,還得要我本身謄寫到做業本上面去(執行本身的作做業方法)。
你就不能直接調用個人作做業方法幫我把答案寫好,把做業作完得了。
讓室友直接把做業寫了
經不住該同窗的軟磨硬泡,「中國好室友」答應了。怎麼實現呢。
再回顧下作做業的全過程
#待解決的做業 String aHomeWork = "1+1=?"; #室友寫出答案 String theAnswer = roomMate.getAnswer(aHomeWork); #該同窗調用,本身把答案寫到做業本。(也便是這個步驟不給調用了) student.doHomeWork(aHomeWork, theAnswer); #作做業必須得調用這個方法,而根據需求這個方法必須由室友去調用。那很顯然,該室友得保持一個該同窗的引用,才能正常調用啊。 #燈燈燈~ #室友說,那你在調用getAnswer方法的時候,除了傳入做業,還須要把本身的引用放裏面。這樣我作完了,直接調用你的作做業方法就好了。 roomMate.getAnswer(aHomeWork,student);
完整代碼
public class Student { public void doHomeWork(String homeWork, String answer) { System.out.println("做業本"); if(answer != null) { System.out.println("做業:"+homeWork+" 答案:"+ answer); } else { System.out.println("做業:"+homeWork+" 答案:"+ "(空白)"); } } public static void main(String[] args) { Student student = new Student(); String aHomeWork = "1+1=?"; RoomMate roomMate = new RoomMate(); roomMate.getAnswer(aHomeWork,student); } }
public class RoomMate { public void getAnswer(String homework, Student student) { if("1+1=?".equals(homework)) { student.doHomeWork(homework, "2"); } else { student.doHomeWork(homework, "(空白)"); } } }
執行程序
做業本 做業:1+1=? 答案:2
回調方法
在上述「讓室友直接把做業寫了」的例子中,其實已經體現了回調的意思。
場景的核心在於這位學生要把做業給作了。
簡單點描述:這位學生告訴室友要作什麼做業,並把本身的引用也給了室友。該室友獲得做業,作完後直接引用該學生並調用其作做業的方法,完成代寫做業的任務。
稍微複雜點描述:
該學生作做業的方法有兩個入參,一個是做業題目(已知),一個是做業答案(未知)。
室友爲了幫助他寫做業提供了一個方法,該方法有兩個入參,一個是做業題目,一個是該學生的引用(解出答案得知道往哪寫)。
程序執行時,該學生只要調用室友的代寫做業方法就好了。一旦室友獲得答案,由於有該學生的引用,因此直接找到對應方法,幫助其完成做業。
再複雜點描述:
學生調用室友的替寫做業方法,註冊了題目和本身的引用。室友的替寫做業方法被調用,則會根據題目完成做業後,再回調該同窗寫做業方法,完成做業。
再抽象點描述:
類A調用類B的方法b(傳入相關信息),類B的方法在執行完後,會將結果寫到(再回調)類A的方法a,完成動做。(其實方法a就是傳說中的回調方法啦)
最抽象的描述:
調用,回調。
接口方式的回調方法
經常使用回調方法的同窗可能會說,我歷來也沒見過直接把對象的引用寫到第一次調用方法裏面的。
嗯,是的,下面就來填上述例子留下的「天坑」(實際項目中常見到)。
問題:在調用方法中直接傳對象引用進去有什麼很差?
只說一點,只是讓別人代寫個方法,犯得上把本身所有暴露給別人嗎。萬一這個別人是競爭對手的接口咋辦。這就是傳說中的後面代碼嗎(/tx)。
總之這樣作是很是不安全的。
所以,最常規的《調用,回調》實現,是(你已經猜到了)使用接口做爲引用(說的不嚴謹)傳入調用的方法裏面。
我認可,怎麼將思路跳轉到使用接口的花了我好長時間。
咱們再看RoomMate類的getAnswer方法。
public class RoomMate { public void getAnswer(String homework, Student student) { if("1+1=?".equals(homework)) { student.doHomeWork(homework, "2"); } else { student.doHomeWork(homework, "(空白)"); } } }
關鍵在於,該方法的用途是來解決某學生提出的某個問題。答案是經過學生的doHomeWork方法回調傳回去的。那假設有個工人也有問題,這位室友該怎麼解決呢。再開個方法,專門接收工人的引用做爲傳參?固然不用,只要你這個引用包含了doHomeWork()方法,那麼不論你是工人、警察仍是環衛工人,直接調用getAnswer()方法就能解決你提的問題。
至此咱們的思路達到了:全部的對象要有同一個方法,因此自熱而然就引出了接口概念。只要這些對象都實現了某個接口就好了,這個接口的做用,僅僅是用來規定那個作做業的方法長什麼樣。這樣工人實現了該接口,那麼就有了默認繼承的作做業方法。工人再把本身的引用拋給該室友的時候,這個室友就不須要改動任何代碼,直接接觸答案,完成任務了。
建立一個作做業的接口,專門規定,須要哪些東西(問題和答案)就能作做業.
public interface DoHomeWork { void doHomeWork(String question, String answer); }
改動下中國好室友的解答方法。任意一個實現了DoHomeWork 接口的someone,都擁有doHomeWork(String question,String answer)的方法。這個方法就是上面已經提到的「回調方法」。someone先調用下好室友的getAnswer()方法,把問題和本身傳進來(此爲調用),好室友把問題解答出以後,調用默認提供的方法,寫完做業。
思考下,由於是以接口做爲參數類型的約定,在普通對象upcast向上轉型以後將只暴露接口描述的那個方法,別人獲取到這個引用,也只能使用這個(回調)方法。至此,遺留的重大安全隱患重要解決。
完整代碼
public class RoomMate { public void getAnswer(String homework, DoHomeWork someone) { if("1+1=?".equals(homework)) { someone.doHomeWork(homework, "2"); } else { someone.doHomeWork(homework, "(空白)"); } } }
package org.futeng.designpattern.callback.test1; public class Worker implements DoHomeWork { @Override public void doHomeWork(String question, String answer) { System.out.println("做業本"); if(answer != null) { System.out.println("做業:"+question+" 答案:"+ answer); } else { System.out.println("做業:"+question+" 答案:"+ "(空白)"); } } public static void main(String[] args) { Worker worker = new Worker(); String question = "1+1=?"; new RoomMate().getAnswer(question, worker); } }
執行程序
做業本 做業:1+1=? 答案:2
至此,調用+回調的文章是否是寫完了呢。
咳咳,還木有。你們喝點茶再忍耐下。(我都寫一天了 - -)
常規使用之匿名內部類
做爲平凡的屁民,實用主義是咱們堅持的生存法則。
因此凡事用不到的技術均可以不學,凡事學了卻不用的技術等於白學。
咱們以前已經定性,中國好室友RoomMate類擁有接受任何人任何問題挑戰的潛質。
自從好室友出名以後,有個不知道什麼工做(類型)的人也來問問題。反正只要實現了回調接口,好室友都能調用你默認繼承的回調方法,那就放馬過來吧。
package org.futeng.designpattern.callback.test1; public class RoomMate { public void getAnswer(String homework, DoHomeWork someone) { if("1+1=?".equals(homework)) { someone.doHomeWork(homework, "2"); } else { someone.doHomeWork(homework, "(空白)"); } } public static void main(String[] args) { RoomMate roomMate = new RoomMate(); roomMate.getAnswer("1+1=?", new DoHomeWork() { @Override public void doHomeWork(String question, String answer) { System.out.println("問題:"+question+" 答案:"+answer); } }); } }
看到稍顯奇怪的roomMate.getAnswer("1+1=?", new DoHomeWork() {的哪一行,其實這裏new的是DoHomeWork接口的一個匿名內部類。這裏我想你們應該本身動腦想一想,調用+反調,這個過程是怎麼實現的了。
至因而否使用匿名內部類是根據具體使用場景決定的。普通類不夠直接,匿名內部類的語法彷佛也不夠友好。
開源工具中對回調方法的使用
上述匿名內部類的示例纔是開源工具中常見到的使用方式。
調用roomMate解答問題的方法(傳進去本身的引用),roomMate解決問題,回調引用裏面包含的回調方法,完成動做。
roomMate就是個工具類,「調用」這個方法你傳進去兩個參數(更多也是一個道理),什麼問題,問題解決了放哪,就好了。該「調用」方法根據問題找到答案,就將結果寫到你指定放置的位置(做爲回調方法的入參)。
試想下,「中國好室友」接收的問題是SQL語句,接收的放置位置是個人引用。你解決問題(執行完SQL),將答案(SQL的反饋結果)直接寫入個人回調方法裏面。回調方法裏面可能包括一個個的字段賦值。可是在調用層面隱藏了不少細節的處理。這是回調方法的一個小小的優點。再換句話說,不須要拿到執行完SQL以後的返回結果一個個來賦值。
SQL的例子
public static List<Person> queryPerson() { QueryRunner queryRunner = new QueryRunner(DataSourceSupport.getDataSource()); return queryRunner.query(" select t.name, t.age from person t ", new ResultSetHandler<List<Person>>(){ List list = new ArrayList<Person>(); @Override public List<Person> handle(ResultSet rs) throws SQLException { while(rs.next()) { Person person = new Person(); person.setName(rs.getString(0)); person.setAge(rs.getInt(1)); list.add(person); } return list; } }); }
回調方法的優點
回調方法最大的優點在於,異步回調,這樣是其最被廣爲使用的緣由。
下面將沿用「中國好室友」 來對回調方法作異步實現。
回調接口不用變
public interface DoHomeWork { void doHomeWork(String question, String answer); }
爲了體現異步的意思,咱們給好室友設置了個較難的問題,但願好室友能多好點時間思考。
Student student = new Student(); String homework = "當x趨向於0,sin(x)/x =?"; #給學生新建個ask方法,該方法中另開一個線程,來等待回調方法的結果反饋。 student.ask(homework, new RoomMate()); #ask方法以下 public void ask(final String homework, final RoomMate roomMate) { new Thread(new Runnable() { @Override public void run() { roomMate.getAnswer(homework, Student.this); } }).start(); goHome(); } #新開的線程純粹用來等待好室友來寫完做用。因爲在好室友類中設置了3秒的等待時間,因此能夠看到goHome方法將先執行。 #意味着該學生在告知好室友作做用後,就能夠作本身的事情去了,不須要同步阻塞去等待結果。 #一旦好室友完成做用,寫入做業本,該現場也就結束運行了。
完整代碼
public class Student implements DoHomeWork{ @Override public void doHomeWork(String question, String answer) { System.out.println("做業本"); if(answer != null) { System.out.println("做業:"+question+" 答案:"+ answer); } else { System.out.println("做業:"+question+" 答案:"+ "(空白)"); } } public void ask(final String homework, final RoomMate roomMate) { new Thread(new Runnable() { @Override public void run() { roomMate.getAnswer(homework, Student.this); } }).start(); goHome(); } public void goHome(){ System.out.println("我回家了……好室友,幫我寫下做業。"); } public static void main(String[] args) { Student student = new Student(); String homework = "當x趨向於0,sin(x)/x =?"; student.ask(homework, new RoomMate()); } }
public class RoomMate { public void getAnswer(String homework, DoHomeWork someone) { if ("1+1=?".equals(homework)) { someone.doHomeWork(homework, "2"); } else if("當x趨向於0,sin(x)/x =?".equals(homework)) { System.out.print("思考:"); for(int i=1; i<=3; i++) { System.out.print(i+"秒 "); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(); someone.doHomeWork(homework, "1"); } else { someone.doHomeWork(homework, "(空白)"); } } }
完結