Java回調(diào)方法詳解
回調(diào)在維基百科中定義為:
在計(jì)算機(jī)程序設(shè)計(jì)中,回調(diào)函數(shù),是指通過(guò)函數(shù)參數(shù)傳遞到其他代碼的,某一塊可執(zhí)行代碼的引用。
其目的是允許底層代碼調(diào)用在高層定義的子程序。
舉個(gè)例子可能更明白一些:以Android中用retrofit進(jìn)行網(wǎng)絡(luò)請(qǐng)求為例,這個(gè)是異步回調(diào)的一個(gè)例子。
在發(fā)起網(wǎng)絡(luò)請(qǐng)求之后,app可以繼續(xù)其他事情,網(wǎng)絡(luò)請(qǐng)求的結(jié)果一般是通過(guò)onResponse與onFailure這兩個(gè)方法返回得到??匆幌孪嚓P(guān)部分的代碼:
call.enqueue(new Callback<HistoryBean>() {
@Override
public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) {
HistoryBean hb = response.body();
if(hb == null) return;
showText.append(hb.isError() + "");
for(HistoryBean.ResultsBean rb : hb.getResults()){
showText.append(rb.getTitle() + "/n");
}
}
@Override
public void onFailure(Call<HistoryBean> call, Throwable t) {
}
});
忽略上面CallBack中的泛型,按照維基百科中的定義,匿名內(nèi)部類(lèi)里面的全部代碼可以看成函數(shù)參數(shù)傳遞到其他代碼的,某一塊可執(zhí)行代碼的引用。 onResponse與onFailure這兩個(gè)方法就是回調(diào)方法。底層的代碼就是已經(jīng)寫(xiě)好不變的網(wǎng)絡(luò)請(qǐng)求部分,高層定義的子程序就是回調(diào),因?yàn)榫唧w的實(shí)現(xiàn)交給了使用者,所以具備了很高的靈活性。上面就是通過(guò)enqueue(Callback callback)這個(gè)方法來(lái)關(guān)聯(lián)起來(lái)的。
回調(diào)方法的步驟
上面說(shuō)的回調(diào)是很通用的概念,放到程序書(shū)寫(xiě)上面,就可以說(shuō):
A類(lèi)中調(diào)用B類(lèi)中的某個(gè)方法C,然后B類(lèi)中在反過(guò)來(lái)調(diào)用A類(lèi)中的方法D,在這里面D就是回調(diào)方法。B類(lèi)就是底層的代碼,A類(lèi)是高層的代碼。
所以通過(guò)上面的解釋?zhuān)覀兛梢酝茢喑鲆恍〇|西,為了表示D方法的通用性,我們采用接口的形式讓D方法稱(chēng)為一個(gè)接口方法,那么如果B類(lèi)要調(diào)用A類(lèi)中的方法D,那勢(shì)必A類(lèi)要實(shí)現(xiàn)這個(gè)接口,這樣,根據(jù)實(shí)現(xiàn)的不同,就會(huì)有多態(tài)性,使方法具備靈活性。
A類(lèi)要調(diào)用B類(lèi)中的某個(gè)方法C,那勢(shì)必A類(lèi)中必須包含B的引用,要不然是無(wú)法調(diào)用的,這一步稱(chēng)之為注冊(cè)回調(diào)接口。那么如何實(shí)現(xiàn)B類(lèi)中反過(guò)來(lái)調(diào)用A類(lèi)中的方法D呢,直接通過(guò)上面的方法C,B類(lèi)中的方法C是接受一個(gè)接口類(lèi)型的參數(shù),那么只需要在C方法中,用這個(gè)接口類(lèi)型的參數(shù)去調(diào)用D方法,就實(shí)現(xiàn)了在B類(lèi)中反過(guò)來(lái)調(diào)用A類(lèi)中的方法D,這一步稱(chēng)之為調(diào)用回調(diào)接口。
這也就實(shí)現(xiàn)了B類(lèi)的C方法中,需要反過(guò)來(lái)再調(diào)用A類(lèi)中的D方法,這就是回調(diào)。A調(diào)用B是直調(diào),可以看成高層的代碼用底層的API,我們經(jīng)常這樣寫(xiě)程序。B調(diào)用A就是回調(diào),底層API需要高層的代碼來(lái)執(zhí)行。
最后,總結(jié)一下,回調(diào)方法的步驟:
- A類(lèi)實(shí)現(xiàn)接口CallBack callback
- A類(lèi)中包含了一個(gè)B的引用
- B中有一個(gè)參數(shù)為CallBack的方法f(CallBack callback)
- 在A類(lèi)中調(diào)用B的方法f(CallBack callback)——注冊(cè)回調(diào)接口
- B就可以在f(CallBack callback)方法中調(diào)用A的方法——調(diào)用回調(diào)接口
回調(diào)的例子
我們以一個(gè)兒子在玩游戲,等媽媽把飯做好在通知兒子來(lái)吃為例,按照上面的步驟去寫(xiě)回調(diào);
上面的例子中,顯然應(yīng)該兒子來(lái)實(shí)現(xiàn)回調(diào)接口,母親調(diào)用回調(diào)接口。所以我們先定義一個(gè)回調(diào)接口,然后讓兒子去實(shí)現(xiàn)這個(gè)回調(diào)接口。
其代碼如下:
public interface CallBack {
void eat();
}
public class Son implements CallBack{
private Mom mom;
//A類(lèi)持有對(duì)B類(lèi)的引用
public void setMom(Mom mom){
this.mom = mom;
}
@Override
public void eat() {
System.out.println("我來(lái)吃飯了");
}
public void askMom(){
//通過(guò)B類(lèi)的引用調(diào)用含有接口參數(shù)的方法。
System.out.println("飯做了嗎?");
System.out.println("沒(méi)做好,我玩游戲了");
new Thread(() -> mom.doCook(Son.this)).start();
System.out.println("玩游戲了中......");
}
}
然后我們還需要定義一個(gè)母親的類(lèi),里面有一個(gè)含有接口參數(shù)的方法doCook
public class Mom {
//在含有接口參數(shù)的方法中利用接口參數(shù)調(diào)用回調(diào)方法
public void doCook(CallBack callBack){
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("做飯中......");
Thread.sleep(5000);
callBack.eat();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
我們通過(guò)一個(gè)測(cè)試類(lèi):
public class Test {
public static void main(String[] args) {
Mom mom = new Mom();
Son son = new Son();
son.setMom(mom);
son.askMom();
}
}
這個(gè)例子就是典型的回調(diào)的例子。Son類(lèi)實(shí)現(xiàn)了接口的回調(diào)方法,通過(guò)askMom這個(gè)方法調(diào)用Mom類(lèi)中的doCook,實(shí)現(xiàn)注冊(cè)回調(diào)接口,相當(dāng)于A類(lèi)中調(diào)用B類(lèi)的代碼C。在Mom類(lèi)中的doCook來(lái)回調(diào)Son類(lèi)中的eat,來(lái)告訴Son類(lèi)中的結(jié)果。
這樣,我們就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的,符合定義的回調(diào)。
回調(diào)例子的進(jìn)一步探索
我們主要看一下Son類(lèi)的代碼:
public class Son implements CallBack{
public Mom mom;
public Son(Mom mom){
this.mom = mom;
}
public void askMom(){
System.out.println("飯做了嗎?");
System.out.println("沒(méi)做好,我玩游戲了");
new Thread(() -> mom.doCook(Son.this)).start();
System.out.println("玩游戲了中......");
}
@Override
public void eat() {
System.out.println("好了,我來(lái)吃飯了");
}
}
這個(gè)類(lèi)里面,除了輸出一些語(yǔ)句之外,真正有用的部分是mom.doCook(Son.this)以及重寫(xiě)eat方法。所以,我們可以通過(guò)匿名內(nèi)部類(lèi)的形式,簡(jiǎn)寫(xiě)這個(gè)回調(diào)。其代碼如下:
public class CallBackTest {
public static void main(String[] args) {
Mom mom = new Mom();
new Thread(()-> mom.doCook(() -> System.out.println("吃飯了......"))).start();
}
}
取消Son類(lèi),直接在主方法中通過(guò)匿名內(nèi)部類(lèi)去實(shí)現(xiàn)eat方法。其實(shí)匿名內(nèi)部類(lèi)就是回調(diào)的體現(xiàn)。
異步回調(diào)與同步回調(diào)
回調(diào)上面我們講了 就是A調(diào)用B類(lèi)中的方法C,然后在方法C里面通過(guò)A類(lèi)的對(duì)象去調(diào)用A類(lèi)中的方法D。
我們?cè)谡f(shuō)一下異步與同步,先說(shuō)同步的概念
同步
同步指的是在調(diào)用方法的時(shí)候,如果上一個(gè)方法調(diào)用沒(méi)有執(zhí)行完,是無(wú)法進(jìn)行新的方法調(diào)用。也就是說(shuō)事情必須一件事情一件事情的做,做完上一件,才能做下一件。
異步
異步相對(duì)于同步,可以不需要等上個(gè)方法調(diào)用結(jié)束,才調(diào)用新的方法。所以,在異步的方法調(diào)用中,是需要一個(gè)方法來(lái)通知使用者方法調(diào)用結(jié)果的。
實(shí)現(xiàn)異步的方式
在Java中最常實(shí)現(xiàn)的異步方式就是讓你想異步的方法在一個(gè)新線程中執(zhí)行。
我們會(huì)發(fā)現(xiàn)一點(diǎn),異步方法調(diào)用中需要一個(gè)方法來(lái)通知使用者調(diào)用結(jié)果,結(jié)合上面所講,我們會(huì)發(fā)現(xiàn)回調(diào)方法就適合做這個(gè)事情,通過(guò)回調(diào)方法來(lái)通知使用者調(diào)用的結(jié)果。
那異步回調(diào)就是A調(diào)用B的方法C時(shí)是在一個(gè)新線程當(dāng)中去做的。
上面的母親通知兒子吃飯的例子,就是一個(gè)異步回調(diào)的例子。在一個(gè)新線程中,調(diào)用doCook方法,最后通過(guò)eat來(lái)接受返回值,當(dāng)然使用lamdba優(yōu)化之后的,本質(zhì)是一樣的。
同步回調(diào)就是A調(diào)用B的方法C沒(méi)有在一個(gè)新線程,在執(zhí)行這個(gè)方法C的時(shí)候,我們什么都不能做,只能等待他執(zhí)行完成。
同步回調(diào)與異步回調(diào)的例子
我們看一個(gè)Android中的一個(gè)同步回調(diào)的例子:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("button","被點(diǎn)擊");
}
});
button通過(guò)setOnClickListener注冊(cè)回調(diào)函數(shù),和上面寫(xiě)的一樣,通過(guò)匿名內(nèi)部類(lèi)的形式將接口的引用傳進(jìn)去。由于button調(diào)用setOnClickListener沒(méi)有新建一個(gè)線程,所以這個(gè)是同步的回調(diào)。
而異步回調(diào),就是我們開(kāi)篇講的那個(gè)例子:
call.enqueue(new Callback<HistoryBean>() {
@Override
public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) {
HistoryBean hb = response.body();
if(hb == null) return;
showText.append(hb.isError() + "");
for(HistoryBean.ResultsBean rb : hb.getResults()){
showText.append(rb.getTitle() + "/n");
}
}
@Override
public void onFailure(Call<HistoryBean> call, Throwable t) {
}
});
這個(gè)enqueue方法是一個(gè)異步方法去請(qǐng)求遠(yuǎn)程的網(wǎng)絡(luò)數(shù)據(jù)。其內(nèi)部實(shí)現(xiàn)的時(shí)候是通過(guò)一個(gè)新線程去執(zhí)行的。
通過(guò)這兩個(gè)例子,我們可以看出同步回調(diào)與異步回調(diào)的使用其實(shí)是根據(jù)不同的需求而設(shè)計(jì)。不能說(shuō)一種取代另一種,像上面的按鈕點(diǎn)擊事件中,如果是異步回調(diào),用戶(hù)點(diǎn)擊按鈕之后其點(diǎn)擊效果不是馬上出現(xiàn),而用戶(hù)又不會(huì)執(zhí)行其他操作,那么會(huì)感覺(jué)很奇怪。而像網(wǎng)絡(luò)請(qǐng)求的異步回調(diào),因?yàn)槭芟抻谡?qǐng)求資源可能不存在,網(wǎng)絡(luò)連接不穩(wěn)定等等原因?qū)е掠脩?hù)不清楚方法執(zhí)行的時(shí)候,所以會(huì)用異步回調(diào),發(fā)起方法調(diào)用之后去做其他事情,然后等回調(diào)的通知。
回調(diào)方法在通信中的應(yīng)用
上面提到的回調(diào)方法,除了網(wǎng)絡(luò)請(qǐng)求框架的回調(diào)除外,其回調(diào)方法都是沒(méi)有參數(shù),下面,我們看一下在回調(diào)方法中加入?yún)?shù)來(lái)實(shí)現(xiàn)一些通信問(wèn)題。
如果我們想要A類(lèi)得到B類(lèi)經(jīng)過(guò)一系列計(jì)算,處理后數(shù)據(jù),而且兩個(gè)類(lèi)是不能通過(guò)簡(jiǎn)單的將B的引用給A類(lèi)就可以得到數(shù)據(jù)的。我們可以考慮回調(diào)。
步驟如下:
- 在擁有數(shù)據(jù)的那個(gè)類(lèi)里面寫(xiě)一個(gè)回調(diào)的接口。-->這里就是B類(lèi)中寫(xiě)一個(gè)回調(diào)接口
- 回調(diào)方法接收一個(gè)參數(shù),這個(gè)參數(shù)就是要得到的數(shù)據(jù)
- 同樣是在這個(gè)類(lèi)里寫(xiě)一個(gè)注冊(cè)回調(diào)的方法。
- 在注冊(cè)回調(diào)方法,用接口的引用去調(diào)用回調(diào)接口,把B類(lèi)的數(shù)據(jù)當(dāng)做參數(shù)傳入回調(diào)的方法中。
- 在A類(lèi)中,用B類(lèi)的引用去注冊(cè)回調(diào)接口,把B類(lèi)中的數(shù)據(jù)通過(guò)回調(diào)傳到A類(lèi)中。
上面說(shuō)的步驟,有點(diǎn)抽象。下面我們看一個(gè)例子,一個(gè)是Client,一個(gè)是Server。Client去請(qǐng)求Server經(jīng)過(guò)耗時(shí)處理后的數(shù)據(jù)。
public class Client{
public Server server;
public String request;
//鏈接Server,得到Server引用。
public Client connect(Server server){
this.server = server;
return this;
}
//Client,設(shè)置request
public Client setRequest(String request){
this.request = request;
return this;
}
//異步發(fā)送請(qǐng)求的方法,lamdba表達(dá)式。
public void enqueue(Server.CallBack callBack){
new Thread(()->server.setCallBack(request,callBack)).start();
}
}
public class Server {
public String response = "這是一個(gè)html";
//注冊(cè)回調(diào)接口的方法,把數(shù)據(jù)通過(guò)參數(shù)傳給回調(diào)接口
public void setCallBack(String request,CallBack callBack){
System.out.println("已經(jīng)收到request,正在計(jì)算當(dāng)中......");
new Thread(() -> {
try {
Thread.sleep(5000);
callBack.onResponse(request + response);
} catch (InterruptedException e) {
e.printStackTrace();
callBack.onFail(e);
}
}).start();
}
//在擁有數(shù)據(jù)的那個(gè)類(lèi)里面寫(xiě)一個(gè)接口
public interface CallBack{
void onResponse(String response);
void onFail(Throwable throwable);
}
}
接下來(lái),我們看一下測(cè)試的例子:
public class CallBackTest {
public static void main(String[] args) {
Client client = new Client();
client.connect(new Server()).setRequest("這個(gè)文件是什么?").enqueue(new Server.CallBack() {
@Override
public void onResponse(String response) {
System.out.println(response);
}
@Override
public void onFail(Throwable throwable) {
System.out.println(throwable.getMessage());
}
});
}
}
結(jié)果如下:
已經(jīng)收到request,正在計(jì)算當(dāng)中...... 這個(gè)文件是什么?這是一個(gè)html
以上就是通過(guò)回調(diào)的方式進(jìn)行通信。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
SpringBoot整合Prometheus如何實(shí)現(xiàn)資源監(jiān)控
本文介紹了如何使用Prometheus監(jiān)控SpringBoot應(yīng)用,Prometheus是一個(gè)開(kāi)源的監(jiān)控和告警工具,SpringBootActuator提供了監(jiān)控和管理SpringBoot應(yīng)用的工具,通過(guò)添加依賴(lài)、配置Actuator和Prometheus,可以實(shí)現(xiàn)對(duì)SpringBoot應(yīng)用的實(shí)時(shí)監(jiān)控2024-12-12
使用JAVA實(shí)現(xiàn)高并發(fā)無(wú)鎖數(shù)據(jù)庫(kù)操作步驟分享
一個(gè)在線2k的游戲,每秒鐘并發(fā)都嚇?biāo)廊?。傳統(tǒng)的hibernate直接插庫(kù)基本上是不可行的。我就一步步推導(dǎo)出一個(gè)無(wú)鎖的數(shù)據(jù)庫(kù)操作,詳情看下文2013-11-11
springboot?publish?event?事件機(jī)制demo分享
這篇文章主要介紹了springboot?publish?event?事件機(jī)制demo,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
SpringMVC中的DispatcherServlet詳細(xì)解析
這篇文章主要介紹了SpringMVC中的DispatcherServlet詳細(xì)解析,DispatcherServlet也是一個(gè)Servlet,它也能通過(guò)Servlet的API來(lái)響應(yīng)請(qǐng)求,從而成為一個(gè)前端控制器,Web容器會(huì)調(diào)用Servlet的doGet()以及doPost()等方法,需要的朋友可以參考下2023-12-12
java使用renderer將pdf按頁(yè)轉(zhuǎn)換為圖片
這篇文章主要為大家詳細(xì)介紹了java使用renderer將pdf按頁(yè)轉(zhuǎn)換為圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
java 實(shí)現(xiàn)輸出隨機(jī)圖片實(shí)例代碼
這篇文章主要介紹了java 實(shí)現(xiàn)輸出隨機(jī)圖片實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-06-06
Jmeter對(duì)響應(yīng)數(shù)據(jù)實(shí)現(xiàn)斷言代碼實(shí)例
這篇文章主要介紹了Jmeter對(duì)響應(yīng)數(shù)據(jù)實(shí)現(xiàn)斷言代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09

