溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中的回調(diào)機(jī)制怎么實(shí)現(xiàn)

發(fā)布時(shí)間:2021-12-21 13:43:41 來(lái)源:億速云 閱讀:149 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容介紹了“Java中的回調(diào)機(jī)制怎么實(shí)現(xiàn)”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

模塊間的調(diào)用

在一個(gè)應(yīng)用系統(tǒng)中,無(wú)論使用何種語(yǔ)言開(kāi)發(fā),必然存在模塊之間的調(diào)用,調(diào)用的方式分為幾種:

(1)同步調(diào)用

同步調(diào)用是最基本并且最簡(jiǎn)單的一種調(diào)用方式,類A的方法a()調(diào)用類B的方法b(),一直等待b()方法執(zhí)行完畢,a()方法繼續(xù)往下走。這種調(diào)用方式適用于方法b()執(zhí)行時(shí)間不長(zhǎng)的情況,因?yàn)閎()方法執(zhí)行時(shí)間一長(zhǎng)或者直接阻塞的話,a()方法的余下代碼是無(wú)法執(zhí)行下去的,這樣會(huì)造成整個(gè)流程的阻塞。

(2)異步調(diào)用

異步調(diào)用是為了解決同步調(diào)用可能出現(xiàn)阻塞,導(dǎo)致整個(gè)流程卡住而產(chǎn)生的一種調(diào)用方式。類A的方法方法a()通過(guò)新起線程的方式調(diào)用類B的方法b(),代碼接著直接往下執(zhí)行,這樣無(wú)論方法b()執(zhí)行時(shí)間多久,都不會(huì)阻塞住方法a()的執(zhí)行。

但是這種方式,由于方法a()不等待方法b()的執(zhí)行完成,在方法a()需要方法b()執(zhí)行結(jié)果的情況下(視具體業(yè)務(wù)而定,有些業(yè)務(wù)比如啟異步線程發(fā)個(gè)微信通知、刷新一個(gè)緩存這種就沒(méi)必要),必須通過(guò)一定的方式對(duì)方法b()的執(zhí)行結(jié)果進(jìn)行監(jiān)聽(tīng)。

在Java中,可以使用Future+Callable的方式做到這一點(diǎn),具體做法可以參見(jiàn)我的這篇文章Java多線程21:多線程下其他組件之CyclicBarrier、Callable、Future和FutureTask。

(3)回調(diào)

1、什么是回調(diào)?
一般來(lái)說(shuō),模塊之間都存在一定的調(diào)用關(guān)系,從調(diào)用方式上看,可以分為三類同步調(diào)用、異步調(diào)用和回調(diào)。同步調(diào)用是一種阻塞式調(diào)用,即在函數(shù)A的函數(shù)體里通過(guò)書寫函數(shù)B的函數(shù)名來(lái)調(diào)用之,使內(nèi)存中對(duì)應(yīng)函數(shù)B的代碼得以執(zhí)行。異步調(diào)用是一種類似消息或事件的機(jī)制解決了同步阻塞的問(wèn)題,例如 A通知 B后,他們各走各的路,互不影響,不用像同步調(diào)用那樣, A通知 B后,非得等到 B走完后, A才繼續(xù)走 。回調(diào)是一種雙向的調(diào)用模式,也就是說(shuō),被調(diào)用的接口被調(diào)用時(shí)也會(huì)調(diào)用對(duì)方的接口,例如A要調(diào)用B,B在執(zhí)行完又要調(diào)用A。

2、回調(diào)的用途
回調(diào)一般用于層間協(xié)作,上層將本層函數(shù)安裝在下層,這個(gè)函數(shù)就是回調(diào),而下層在一定條件下觸發(fā)回調(diào)。例如作為一個(gè)驅(qū)動(dòng),是一個(gè)底層,他在收到一個(gè)數(shù)據(jù)時(shí),除了完成本層的處理工作外,還將進(jìn)行回調(diào),將這個(gè)數(shù)據(jù)交給上層應(yīng)用層來(lái)做進(jìn)一步處理,這在分層的數(shù)據(jù)通信中很普遍。

多線程中的“回調(diào)”

Java多線程中可以通過(guò)callable和future或futuretask結(jié)合來(lái)獲取線程執(zhí)行后的返回值。實(shí)現(xiàn)方法是通過(guò)get方法來(lái)調(diào)用callable的call方法獲取返回值。

其實(shí)這種方法本質(zhì)上不是回調(diào),回調(diào)要求的是任務(wù)完成以后被調(diào)用者主動(dòng)回調(diào)調(diào)用者的接口。而這里是調(diào)用者主動(dòng)使用get方法阻塞獲取返回值。

public class 多線程中的回調(diào) {
    //這里簡(jiǎn)單地使用future和callable實(shí)現(xiàn)了線程執(zhí)行完后
    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";
            }
        });
        //手動(dòng)阻塞調(diào)用get通過(guò)call方法獲得返回值。
        System.out.println(future.get());
        //需要手動(dòng)關(guān)閉,不然線程池的線程會(huì)繼續(xù)執(zhí)行。
        executor.shutdown();
    //使用futuretask同時(shí)作為線程執(zhí)行單元和數(shù)據(jù)請(qǐng)求單元。
    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回調(diào)機(jī)制實(shí)戰(zhàn)

曾經(jīng)自己偶爾聽(tīng)說(shuō)過(guò)回調(diào)機(jī)制,隱隱約約能夠懂一些意思,但是當(dāng)讓自己寫一個(gè)簡(jiǎn)單的示例程序時(shí),自己就傻眼了。隨著工作經(jīng)驗(yàn)的增加,自己經(jīng)常聽(tīng)到這兒使用了回調(diào),那兒使用了回調(diào),自己是時(shí)候好好研究一下Java回調(diào)機(jī)制了。網(wǎng)上關(guān)于Java回調(diào)的文章一抓一大把,但是看完總是云里霧里,不知所云,特別是看到抓取別人的代碼走兩步時(shí),總是現(xiàn)眼。于是自己決定寫一篇關(guān)于Java機(jī)制的文章,以方便大家和自己更深入的學(xué)習(xí)Java回調(diào)機(jī)制。

首先,什么是回調(diào)函數(shù),引用百度百科的解釋:回調(diào)函數(shù)就是一個(gè)通過(guò)函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù)。回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對(duì)該事件或條件進(jìn)行響應(yīng)[2].

不好意思,上述解釋我看了好幾遍,也沒(méi)理解其中深刻奧秘,相信一些讀者你也一樣。光說(shuō)不練假把式,咱們還是以實(shí)戰(zhàn)理解脈絡(luò)。

實(shí)例一 : 同步調(diào)用

本文以底層服務(wù)BottomService和上層服務(wù)UpperService為示例,利用上層服務(wù)調(diào)用底層服務(wù),整體執(zhí)行過(guò)程如下:

第一步: 執(zhí)行UpperService.callBottomService();

第二步: 執(zhí)行BottomService.bottom();

第三步:執(zhí)行UpperService.upperTaskAfterCallBottomService()

1.1 同步調(diào)用代碼

同步調(diào)用時(shí)序圖:

Java中的回調(diào)機(jī)制怎么實(shí)現(xiàn)

同步調(diào)用時(shí)序圖

1.1.1 底層服務(wù)類:BottomService.java

package synchronization.demo;
/**
* Created by lance on 2017/1/19.
*/
public class BottomService {
public String bottom(String param) {
try { //  模擬底層處理耗時(shí),上層服務(wù)需要等待
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return param +" BottomService.bottom() execute -->";
}
}

1.1.2 上層服務(wù)接口: 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 上層服務(wù)接口實(shí)現(xiàn)類: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測(cè)試類: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執(zhí)行必須等待callBottomService()調(diào)用BottomService.bottom()方法返回后才能夠執(zhí)行
upperService.upperTaskAfterCallBottomService(result);
System.out.println("=============== callBottomService end ====================:" + new Date());
}
}

1.1.5 輸出結(jié)果:

=============== 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

注意輸出結(jié)果:

是同步方式,Test調(diào)用callBottomService()等待執(zhí)行結(jié)束,然后再執(zhí)行下一步,即執(zhí)行結(jié)束。callBottomService開(kāi)始執(zhí)行時(shí)間為Thu Jan 19 14:59:58 CST 2017,執(zhí)行結(jié)束時(shí)間為Thu Jan 19 15:00:01 CST 2017,耗時(shí)3秒鐘,與模擬的耗時(shí)時(shí)間一致,即3000毫秒。

實(shí)例二:由淺入深

前幾天公司面試有問(wèn)道java回調(diào)的問(wèn)題,因?yàn)檫@方面也沒(méi)有太多研究,所以回答的含糊不清,這回特意來(lái)補(bǔ)習(xí)一下。看了看網(wǎng)上的回調(diào)解釋和例子,都那么的繞口,得看半天才能繞回來(lái),其實(shí)吧,回調(diào)是個(gè)很簡(jiǎn)單的機(jī)制。在這里我用簡(jiǎn)單的語(yǔ)言先來(lái)解釋一下:假設(shè)有兩個(gè)類,分別是A和B,在A中有一個(gè)方法a(),B中有一個(gè)方法b();在A里面調(diào)用B中的方法b(),而方法b()中調(diào)用了方法a(),這樣子就同時(shí)實(shí)現(xiàn)了b()和a()兩個(gè)方法的功能。

疑惑:為啥這么麻煩,我直接在類A中的B.b()方法下調(diào)用a()方法就行了唄。
解答:回調(diào)更像是一個(gè)約定,就是如果我調(diào)用了b()方法,那么就必須要回調(diào),而不需要顯示調(diào)用
一、Java的回調(diào)-淺
我們用例子來(lái)解釋:小明和小李相約一起去吃早飯,但是小李起的有點(diǎn)晚要先洗漱,等小李洗漱完成后,通知小明再一起去吃飯。小明就是類A,小李就是類B。一起去吃飯這個(gè)事件就是方法a(),小李去洗漱就是方法b()。

public class XiaoMing {    
   //小明和小李一起吃飯
   public void eatFood() {
      XiaoLi xl = new XiaoLi();
      //A調(diào)用B的方法
      xl.washFace();
   }
   public void eat() {
      System.out.print("小明和小李一起去吃大龍蝦");
   }
}
那么怎么讓小李洗漱完后在通知小明一起去吃飯呢
public class XiaoMing {    
   //小明和小李一起吃飯
   public void eatFood() {
      XiaoLi xl = new XiaoLi();
      //A調(diào)用B的方法
      xl.washFace();
      eat();
   }
   public void eat() {
      System.out.print("小明和小李一起去吃大龍蝦");
   }
}

不過(guò)上面已經(jīng)說(shuō)過(guò)了這個(gè)不是回調(diào)函數(shù),所以不能這樣子,正確的方式如下

public class XiaoLi{//小李
   public void washFace() {
    System.out.print("小李要洗漱");
    XiaoMing xm = new XiaoMing();
        //B調(diào)用A的方法
    xm.eat();//洗漱完后,一起去吃飯
   }
}

這樣子就可以實(shí)現(xiàn)washFace()同時(shí)也能實(shí)現(xiàn)eat()。小李洗漱完后,再通知小明一起去吃飯,這就是回調(diào)。

二、Java的回調(diào)-中
可是細(xì)心的伙伴可能會(huì)發(fā)現(xiàn),小李的代碼完全寫死了,這樣子的場(chǎng)合可能適用和小明一起去吃飯,可是假如小李洗漱完不吃飯了,想和小王上網(wǎng)去,這樣子就不適用了。其實(shí)上面是偽代碼,僅僅是幫助大家理解的,真正情況下是需要利用接口來(lái)設(shè)置回調(diào)的?,F(xiàn)在我們繼續(xù)用小明和小李去吃飯的例子來(lái)講講接口是如何使用的。

小明和小李相約一起去吃早飯,但是小李起的有點(diǎn)晚要先洗漱,等小李洗漱完成后,通知小明再一起去吃飯。小明就是類A,小李就是類B。不同的是我們新建一個(gè)吃飯的接口EatRice,接口中有個(gè)抽象方法eat()。在小明中調(diào)用這個(gè)接口,并實(shí)現(xiàn)eat();小李聲明這個(gè)接口對(duì)象,并且調(diào)用這個(gè)接口的抽象方法。這里可能有點(diǎn)繞口,不過(guò)沒(méi)關(guān)系,看看例子就很清楚了。

EatRice接口:

public interface EatRice {
   public void eat(String food);
}
小明:
public class XiaoMing implements EatRice{//小明
   //小明和小李一起吃飯
   public void eatFood() {
    XiaoLi xl = new XiaoLi();
    //A調(diào)用B的方法
    xl.washFace("大龍蝦", this);//this指的是小明這個(gè)類實(shí)現(xiàn)的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調(diào)用了A的方法
    er.eat(food);
   }
}
測(cè)試Demo:
public class demo {
   public static void main(String args[]) {
    XiaoMing xm = new XiaoMing();
    xm.eatFood();
   }
}

測(cè)試結(jié)果:

這樣子就通過(guò)接口的形式實(shí)現(xiàn)了軟編碼。通過(guò)接口的形式我可以實(shí)現(xiàn)小李洗漱完后,和小王一起去上網(wǎng)。代碼如下

public class XiaoWang implements EatRice{//小王
   //小王和小李一起去上網(wǎng)
   public void eatFood() {
    XiaoLi xl = new XiaoLi();
    //A調(diào)用B的方法
    xl.washFace("輕舞飛揚(yáng)上網(wǎng)", this);
   }
   @Override
   public void eat(String bar) {
    // TODO Auto-generated method stub
    System.out.println("小王和小李一起去" + bar);
   }
}

實(shí)例三:Tom做題

數(shù)學(xué)老師讓Tom做一道題,并且Tom做題期間數(shù)學(xué)老師不用盯著Tom,而是在玩手機(jī),等Tom把題目做完后再把答案告訴老師。

1 數(shù)學(xué)老師需要Tom的一個(gè)引用,然后才能將題目發(fā)給Tom。

2 數(shù)學(xué)老師需要提供一個(gè)方法以便Tom做完題目以后能夠?qū)⒋鸢父嬖V他。

3 Tom需要數(shù)學(xué)老師的一個(gè)引用,以便Tom把答案給這位老師,而不是隔壁的體育老師。

回調(diào)接口,可以理解為老師接口

    //回調(diào)指的是A調(diào)用B來(lái)做一件事,B做完以后將結(jié)果告訴給A,這期間A可以做別的事情。
    //這個(gè)接口中有一個(gè)方法,意為B做完題目后告訴A時(shí)使用的方法。
    //所以我們必須提供這個(gè)接口以便讓B來(lái)回調(diào)。
    //回調(diào)接口,
    public interface CallBack {
        void tellAnswer(int res);
    }

數(shù)學(xué)老師類

    //老師類實(shí)例化回調(diào)接口,即學(xué)生寫完題目之后通過(guò)老師的提供的方法進(jìn)行回調(diào)。
    //那么學(xué)生如何調(diào)用到老師的方法呢,只要在學(xué)生類的方法中傳入老師的引用即可。
    //而老師需要指定學(xué)生答題,所以也要傳入學(xué)生的實(shí)例。
public class Teacher implements CallBack{
    private Student student;
    Teacher(Student student) {
        this.student = student;
    }
    void askProblem (Student student, Teacher teacher) {
        //main方法是主線程運(yùn)行,為了實(shí)現(xiàn)異步回調(diào),這里開(kāi)啟一個(gè)線程來(lái)操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                student.resolveProblem(teacher);
            }
        }).start();
        //老師讓學(xué)生做題以后,等待學(xué)生回答的這段時(shí)間,可以做別的事,比如玩手機(jī).\
        //而不需要同步等待,這就是回調(diào)的好處。
        //當(dāng)然你可以說(shuō)開(kāi)啟一個(gè)線程讓學(xué)生做題就行了,但是這樣無(wú)法讓學(xué)生通知老師。
        //需要另外的機(jī)制去實(shí)現(xiàn)通知過(guò)程。
        // 當(dāng)然,多線程中的future和callable也可以實(shí)現(xiàn)數(shù)據(jù)獲取的功能。
        for (int i = 1;i < 4;i ++) {
            System.out.println("等學(xué)生回答問(wèn)題的時(shí)候老師玩了 " + i + "秒的手機(jī)");
        }
    }
    @Override
    public void tellAnswer(int res) {
        System.out.println("the answer is " + res);
    }
}

學(xué)生接口

    //學(xué)生的接口,解決問(wèn)題的方法中要傳入老師的引用,否則無(wú)法完成對(duì)具體實(shí)例的回調(diào)。
    //寫為接口的好處就是,很多個(gè)學(xué)生都可以實(shí)現(xiàn)這個(gè)接口,并且老師在提問(wèn)題時(shí)可以通過(guò)
    //傳入List<Student>來(lái)聚合學(xué)生,十分方便。
public interface Student {
    void resolveProblem (Teacher teacher);
}

學(xué)生Tom

public class Tom implements Student{
    @Override
    public void resolveProblem(Teacher teacher) {
        try {
            //學(xué)生思考了3秒后得到了答案,通過(guò)老師提供的回調(diào)方法告訴老師。
            Thread.sleep(3000);
            System.out.println("work out");
            teacher.tellAnswer(111);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

測(cè)試類

public class Test {
    public static void main(String[] args) {
        //測(cè)試
        Student tom = new Tom();
        Teacher lee = new Teacher(tom);
        lee.askProblem(tom, lee);
        //結(jié)果
//        等學(xué)生回答問(wèn)題的時(shí)候老師玩了 1秒的手機(jī)
//        等學(xué)生回答問(wèn)題的時(shí)候老師玩了 2秒的手機(jī)
//        等學(xué)生回答問(wèn)題的時(shí)候老師玩了 3秒的手機(jī)
//        work out
//        the answer is 111
    }
}

“Java中的回調(diào)機(jī)制怎么實(shí)現(xiàn)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI