天天看點

java的回調、閉包、内部類

閉包是可以包含自由(未綁定)變量的代碼塊;這些變量不是在這個代碼塊或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義。“閉包”一詞來源于以下兩者的結合:要執行的代碼塊(由于自由變量的存在,相關變量引用沒有釋放)和為自由變量提供綁定的計算環境(作用域)。在 Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby 和 Python 等語言中都能找到對閉包不同程度的支援。

閉包的價值在于可以作為函數對象或者匿名函數,對于類型系統而言這就意味着不僅要表示資料還要表示代碼。支援閉包的多數語言都将函數作為第一級對象,就是說這些函數可以存儲到變量中、作為參數傳遞給其他函數,最重要的是能夠被函數動态地建立和傳回。

首先讓我們先來看一下在Javascript中閉包(Closure)

[javascript]  view plain  copy
  1. function a(){  
  2.  var i=0;  
  3.  function b(){  
  4.  alert(++i);  
  5.  }  
  6.  return b;  
  7.  }  
  8.  var c = a();  
  9.  c();  

這段代碼有兩個特點:

1、函數b嵌套在函數a内部;

2、函數a傳回函數b。

這樣在執行完var c=a()後,變量c實際上是指向了函數b,再執行c()後就會彈出一個視窗顯示i的值(第一次為1)。這段代碼其實就建立了一個閉包,為什麼?因為函數a外的變量c引用了函數a内的函數b,就是說:

當函數a的内部函數b被函數a外的一個變量引用的時候,就建立了一個閉包。

  簡而言之,閉包的作用就是在a執行完并傳回後,閉包使得Javascript的垃圾回收機制GC不會收回a所占用的資源,因為a的内部函數b的執行需要依賴a中的變量。

在上面的例子中,由于閉包的存在使得函數a傳回後,a中的i始終存在,這樣每次執行c(),i都是自加1後alert出i的值。

那麼我們來想象另一種情況,如果a傳回的不是函數b,情況就完全不同了。因為a執行完後,b沒有被傳回給a的外界,隻是被a所引用,而此時a也隻會被b引用,是以函數a和b互相引用但又不被外界打擾(被外界引用),函數a和b就會被GC回收。

 閉包的應用場景

1、保護函數内的變量安全。以最開始的例子為例,函數a中i隻有函數b才能通路,而無法通過其他途徑通路到,是以保護了i的安全性。

2、在記憶體中維持一個變量。依然如前例,由于閉包,函數a中i的一直存在于記憶體中,是以每次執行c(),都會給i自加1。

以上兩點是閉包最基本的應用場景,很多經典案例都源于此。

Java 語言本身還沒有正式支援閉包,但它卻允許模拟閉包。可以使用匿名的内部類來實作閉包。可以了解為因為它不僅包含外圍類對象(建立内部類的作用域)的資訊,還自動擁有一個指向此外圍類對象的引用,在此作用城内,内部類有權操作所有的成員,包括private成員。

回調函數(callback Function),顧名思義,用于回調的函數。回調函數隻是一個功能片段,由使用者按照回調函數調用約定來實作的一個函數。回調函數是一個工作流的一部分,由工作流來決定函數的調用(回調)時機。回調函數包含下面幾個特性:

1、屬于工作流的一個部分;

2、必須按照工作流指定的調用約定來申明(定義);

3、他的調用時機由工作流決定,回調函數的實作者不能直接調用回調函數來實作工作流的功能;

回調機制是一種常見的設計模型,他把工作流内的某個功能,按照約定的接口暴露給外部使用者,為外部使用者提供資料,或要求外部使用者提供資料。

java回調機制:

軟體子產品之間總是存在着一定的接口,從調用方式上,可以把他們分為三類:同步調用、回調和異步調用。

同步調用:一種阻塞式調用,調用方要等待對方執行完畢才傳回,它是一種單向調用;

回調:一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;

異步調用:一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。

回調和異步調用的關系非常緊密:使用回調來實作異步消息的注冊,通過異步調用來實作消息的通知。

看下面的例子:

[java]  view plain  copy
  1. package callback.example;  
  2. public interface ICallBack {  
  3. public void postExec();//需要回調的方法  
  4.  }  

另外的一個類:

[java]  view plain  copy
  1. package callback.example;  
  2. public class FooBar { //組合聚合原則  
  3. private ICallBack callBack;  
  4. public void setCallBack(ICallBack callBack)  
  5. { this.callBack = callBack; doSth(); }  
  6. public void doSth()  
  7. { callBack.postExec(); } }  

第二個類在測試類裡面,是一個匿名類:

[java]  view plain  copy

  1.     package callback.example;  
  2.     public class Test {  
  3.     public static void main(String[] args)  
  4.     { FooBar foo = new FooBar();  
  5.     foo.setCallBack(new ICallBack() {  
  6.     public void postExec()   
  7. { System.out.println("在Test類中實作但不能被Test的對象引用,而由FooBar對象調用"); } }); } }  

以上代碼中:

1.兩個類:匿名類和FooBar

2.匿名類實作接口ICallBack(在test測試的main方法中用匿名類的形式實作)

3.FooBar 擁有一個參數為ICallBack接口類型的函數setCallBack(ICallBacko) 

4.匿名類運作時調用FooBar中setCallBack函數,以自身傳入參數

5.FooBar已取得匿名類,就可以随時回調匿名類中所實作的ICallBack接口中的方法

首先回調方法的概念與“構造方法”的概念是不一樣的,它不是指java中某個具有特殊意義或用途的方法。

稱它為方法的“回調”更恰當一些,它是指方法的一種調用方式。任何一個被“回調”的方法,皆可稱之為“回調方法”

方法的回調通常發生在“java接口”和“抽象類”的使用過程中。

假設有接口名為ICallBack其中有方法名為postExec()

有類Myclass實作了該接口,也就是一定實作了postExec()這個方法。現在有另一個類FooBar它有個方法setCallBack(ICallBackcallBack) ,并且setCallBack方法調用了callBack的postExec()方法。

如果現在,我們使用一個Myclass的執行個體myClass,将它作為參數帶入到setCallBack(ICallBackcallBack)方法中,我們就說setCallBack(ICallBackcallBack)方法回調了myClass的postExec()方法。

Java最引人争議的問題之一就是,人們認為Java應該包含某種類似指針的機制,以允許回調(callback)。通過回調,對象能夠攜帶一些資訊,這些資訊允許它在稍後的某個時刻調用初始的對象。

如果回調是通過指針實作的,那麼就隻能寄希望于程式員不會誤用該指針。然而,您應該已經了解到,Java更小心仔細,是以沒有在語言中包括指針。

通過内部類提供閉包的功能是優良的解決方案,它比指針更靈活、更安全。

再看下面的例子:

[java]  view plain  copy
  1. // innerclasses/Callbacks.java  
  2. // Using inner classes for callbacks  
  3. package innerclasses  
  4. interface Incrementable {  
  5. void increment();  
  6. }  
  7.    // Very simple to just implement the interface  
  8. class Callee1 implements Incrementable {  
  9. private int i = 0;  
  10. public void increment() {  
  11. i++;  
  12. System.out.println(i);  
  13.  }  
  14.  }  
  15. class MyIncrement {  
  16. public void increment() {System.out.println("Other operation");}  
  17. static void f(MyIncrement mi) {mi.increment();}  
  18. }  
  19. // If your class must implement increment() in  
  20. // some other way, you must use an inner class:  
  21. //個人了解,這裡increment方法需要作為一個回調方法,但是它已經有了increment方法,此時為了避免覆寫不能通過實作 Incrementable接口解決問題,是以這裡需要通過建立内部類的方式解決,内部類可以實作Incrementable接口,
  22. 同時它又是一個閉包,它完整儲存了建立它的類的自由變量的資訊,而且它和外界也産生了關聯。此時可以通過傳回這個閉包的方法來達到回調increment方法的目的。這是很安全的,因為閉包隻有您規定的功能。
  23. class Callee2 extends MyIncrement {  
  24. private int i=0;  
  25. public void increment() {  
  26. super.increment();  
  27. i++;  
  28. System.out.println(i);  
  29. }  
  30. private class Closure implements Incrementable {  
  31. public void increment() {  
  32.  // Specify outer-class method, otherwise  
  33.  // you'd get an infinite recursion  
  34. Callee2.this.increment();  
  35.  }  
  36.  }  
  37. Incrementable getCallbackReference() {  
  38. return new Closure();  
  39. }  
  40.  }  
  41. class Caller {  
  42. private Incrementable callbackReference;  
  43. Caller(Incrementablecbh) {callbackReference = cbh;}  
  44. void go() {callbackReference.increment();}  
  45. }  
  46. public class Callbacks {  
  47. public static void main(String[] args) {  
  48. Callee1 c1 = new Callee1();  
  49.  Callee2 c2 = new Callee2();  
  50. MyIncrement.f(c2);  
  51. Caller caller1 = new Caller(c1);  
  52. Caller caller2 = new Caller(c2.getCallbackReference());  
  53. caller1.go();  
  54. caller1.go();  
  55. caller2.go();  
  56. caller2.go();  
  57.  }  
  58. }  

輸出:

 Other operation

1

1

2

Other operation

2

Other operation

3

這個例子進一步展示了外圍類實作一個接口與内部類實作此接口之間的差別。就代碼而言,Callee1是簡單的解決方式。Callee2繼承自MyIncrement,後者已經有了一個不同的increment()方法,并且與Incrementable接口期望的increment()方法完全不相關。

是以如果Callee2繼承了MyIncrement,就不能為了Incrementable的用途而覆寫increment()方法,于是隻能使用内部類獨立地實作Incrementable。還要注意,當建立了一個内部類時,并沒有在外圍類的接口中添加東西,也沒有修改外圍類的接口。

注意,在Callee2中除了getCallbackReference()以外,其他成員都是private的。要想建立與外部世界的任何連接配接,interface Incrementable都是必需的。在這裡可以看到,interface是如何允許接口與接口的實作完全獨立的。

内部類Closure實作了Incrementable,以提供一個返Callee2的“鈎子”(hook)——而且是一個安全的鈎子。無論誰獲得此Incrementable的引用,都隻能調用increment(),除此之外沒有其他功能(不像指針那樣,允許您做很多事情)。

   Caller的構造器需要一個Incrementable的引用作為參數(雖然可以在任意時刻捕獲回調引用),然後在以後的某個時刻,(Caller對象可以使用此引用回調Callee類。

回調的價值在于它的靈活性——可以在運作時動态地決定需要調用什麼方法。