首頁  >  文章  >  Java  >  詳解Java的回呼機制

詳解Java的回呼機制

高洛峰
高洛峰原創
2017-01-24 11:52:351353瀏覽

模組之間總是存在這一定的接口,從呼叫方式上看,可以分為三類:同步呼叫、回調和非同步呼叫。下面著重詳解回調機制。

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 溝通。這樣就是回調的模型了。以下是一般回調的類別圖: 

詳解Java的回呼機制

首先我們要有一個回呼的介面CallbackInterface 

CallbackInterface.java

public interface CallbackInterface {
  public boolean check(int result);
}

進行溝通的,所以這裡專案經理需要實現上面的回調介面: 

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為Programmer 安排工作

第1 次研究的結果

第2 次研究的結果
第3 次研究的結果

第4 次研究的結果

第5 次研究的結果

調查任務結束

為Programmer 安排工作已經完成,Manager 做其他的事情去了。


2.2 非同步回調

還是上面的例子,你的專案經理不可能要一直等你研究的結果。而是把這個任務交給你之後,他就不管了,他做他的,你做你的。所以,這裡需要對回呼的函數進行非同步處理。
所以,這裡我們需要修改Programmer 類別的程式碼,修改如下: 


Programmer.java

public class Boss {
 
  public static void main(String[] args) {
    Manager manager = new Manager(new Programmer());
    manager.entrust();
  }
}

   

mer的事情去了。

第1 次研究的結果

第2 次研究的結果
第3 次研究的結果
第4 次研究的結果

第5 次研究的結果

研究任務結束

2.3 閉包與回調

套件(closure)是一個可呼叫的對象,它記錄了一些訊息,這些訊息來自於創建它的作用域。

2.3.1 普通呼叫

首先,我們可以看看在正常情況下的呼叫是怎麼進行的。 
Incrementable.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("调研任务结束");
    }
  }
}



這是一個普通的介面(在普通調用裡只是普通接口,在回調中就是回調接口,這一點應該很好理解吧)。

Callee1.java
interface Incrementable {
  void increment();
}

Callbacks.java

class Callee1 implements Incrementable {
 
  private int i = 0;
 
  @Override
  public void increment() {
    i++;
    System.out.println(i);
  }
 
}

Callbacks 是一個測試客戶端類,沒啥好說的,直接看上面的程式碼。


2.3.2 回呼初試

上面的普通呼叫也沒啥好說的,因為這對於一個正常的 Java 程式設計師來說都應該是想都不用想就可以搞定的事情。 

現在如果要構成回調,那麼對於程式的結構或是邏輯的思考上都不可能只有一個被呼叫者(被回調的物件 Callee1),還需要一個被呼叫者物件。呼叫者可以像下面這樣來寫: 

Caller.java

public class Callbacks {
  public static void main(String[] args) {
    Callee1 callee1 = new Callee1();
    callee1.increment();
  }
}

   

這裡Caller 持有一個回呼接口的引用callbackReference,就像在上面說到的程序經理需要持有一個項目的引用程序員,這樣就可以透過這個引用來與專案經理溝通。這裡的 callbackReference 也正是扮演了這個角色。 

現在我們來看看測試類別的編寫: 


Callbacks.java

class Caller {
 
  private Incrementable callbackReference;
 
  public Caller(Incrementable _callbackReference) {
    callbackReference = _callbackReference;
  }
 
  void go() {
    callbackReference.increment();
  }
}

对于到目前为止的程序代码,完全可以对比上面项目经理安排程序员调研技术难题的代码。有异曲同工之妙。

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();
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持PHP中文网。

更多詳解Java的回呼機制相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn