java為什麼匿名內部類的引數引用時final?
https://www.zhihu.com/question/21395848
https://www.zhihu.com/question/24084277/answer/110176733
喜歡看生肉的同學就不用看我的回答了,直接看R大的三篇回答,尤其是第一篇後面的回覆部分。
我只是試著用大白話做個簡單的整理,希望能更容易理解一點。
關於物件與閉包的關係的一個有趣小故事
(這篇的精華在後面的回覆,小故事可以跳過)
JVM的規範中允許程式語言語義中建立閉包(closure)嗎? - RednaxelaFX 的回答
為什麼Java閉包不能通過返回值之外的方式向外傳遞值? - RednaxelaFX 的回答
1.
閉包(Closure)
什麼是閉包,大白話不怎麼嚴謹的說就是:
- 一個依賴於外部環境自由變數的函式
- 這個函式能夠訪問外部環境裡的自由變數
對內部函式function(x)來講,y就是自由變數,而且function(x)的返回值,依賴於這個外部自由變數
y。而往上推一層,外圍Add(y)函式正好就是那個包含自由變數y的環境。而且Javascript的語法允
許內部函式function(x)訪問外部函式Add(y)的區域性變數。滿足這三個條件,所以這個時候,外部函
數Add(y)對內部函式function(x)構成了閉包。
閉包的結構,如果用λ演算表示式來寫,就是多引數的Currying技術。
>λx.λy.x+y
但在Java中我們看不到這樣的結構。因為Java主流語法不允許這樣的直接的函式套嵌和跨域訪問變數。
2.
類和物件
但Java中真的不存在閉包嗎?正好相反,Java到處都是閉包,所以反而我們感覺不出來在使用閉
包。因為Java的“物件”其實就是一個閉包。其實無論是閉包也好,物件也好,都是一種資料封裝的
手段。看下面這個類
看上去x在函式add()的作用域外面,但是通過Add類例項化的過程,變數”x“和數值”2“之間已經綁
定了,而且和函式add()也已經打包在一起。add()函式其實是透過this關鍵字來訪問物件的成員字
段的。
如果對閉包有疑問,可以看這個更詳細的回答:
3.
Java內部類是閉包:包含指向外部類的指標
那Java裡有沒有除了例項物件之外的閉包結構?Java中的內部類就是一個典型的閉包結構。例子如下,
下圖畫的就是上面程式碼的結構。內部類(Inner
Class)通過包含一個指向外部類的引用,做到自
由訪問外部環境類的所有欄位,變相把環境中的自由變數封裝到函式裡,形成一個閉包。
4.
彆扭的匿名內部類
但Java匿名內部類就做得比較尷尬。下面這個例子中,getAnnoInner負責返回一個匿名內部類的引用。
匿名內部類因為是匿名,所以不能顯式地宣告建構函式,也不能往建構函式裡傳引數。不但返回的只是個叫AnnoInner的介面,而且還沒有和它外圍環境getAnnoInner()方法的區域性變數x和y構成任何類的結構。但它的addXYZ()函式卻直接使用了x和y這兩個自由變數來計算結果。這就說明,外部方法getAnnoInner()事實上已經對內部類AnnoInner構成了一個閉包。
但這裡彆扭的地方是這兩個x和y都必須用final修飾,不可以修改。如果用一個changeY()函式試圖修改外部getAnnoInner()函式的成員變數y,編譯器通不過,
error:
cannot assign a value to final variable y
這是為什麼呢?因為這裡Java編譯器支援了閉包,但支援地不完整。說支援了閉包,是因為編譯器編譯的時候其實悄悄對函式做了手腳,偷偷把外部環境方法的x和y區域性變數,拷貝了一份到匿名內部類裡。如下面的程式碼所示。
所以用R大回答裡的原話說就是:
Java編譯器實現的只是capture-by-value,並沒有實現capture-by-reference。
而只有後者才能保持匿名內部類和外部環境區域性變數保持同步。
但Java又不肯明說,只能粗暴地一刀切,就說既然內外不能同步,那就不許大家改外圍的區域性變數。
5.
其他和匿名內部類相似的結構
《Think
in Java》書裡,只點出了匿名內部類來自外部閉包環境的自由變數必須是final的。但實際上,其他幾種不太常用的內部類形式,也都有這個特性。
比如在外部類成員方法內部的內部類。
比如在一個程式碼塊block裡的內部類。
相關文章
- [短文速讀-3] 內部匿名類使用外部變數為什麼要加final變數
- java培訓教程:什麼是匿名內部類?怎樣建立匿名內部類?Java
- 內部類是什麼?匿名內部類又是什麼?
- java內部類,為什麼需要內部類?Java
- Java內部類詳解--匿名內部類Java
- Java類與匿名內部類Java
- Java中的匿名內部類及內部類的二三事Java
- java內部類,區域性內部類,靜態內部類,匿名內部類Java
- 10-Java內部類——成員內部類、區域性內部類、匿名內部類Java
- 匿名內部類
- java匿名內部類:“ 儂好,世界”Java
- 匿名內部類的使用
- 匿名內部類理解
- Java之區域性匿名內部類物件Java物件
- Java_介面回撥與匿名內部類Java
- 關於java匿名內部類初始化法Java
- 用匿名內部類實現 Java 同步回撥Java
- 匿名內部類中關於new Runnable ( )
- Java 內部類Java
- Java內部類Java
- 匿名內部類方式實現執行緒的建立執行緒
- java中的內部類Java
- 在Java中,final修飾的類有什麼特點Java
- Lambda對比匿名內部類,Lambda是什麼,Lambda該怎麼用,Lambda使用過程中有什麼需要注意的?
- Java內部類詳解-- 成員內部類Java
- 10、Java——內部類Java
- Java內部類詳解--區域性內部類Java
- scala_繼承、型別判斷、抽象類、匿名內部類繼承型別抽象
- Java 的抽象類, 介面以及內部類Java抽象
- Java基礎內部類4-內部類進階Java
- 面試題:連結串列為什麼使用內部類實現?面試題
- Java-InnerClass內部類Java
- Java 內部類詳解Java
- 淺談java內部類Java
- Java內部類詳解Java
- Java中內部類的騷操作Java
- 淺談Java中的內部類Java
- lambda表示式在oj中會比匿名內部類慢
- Java和ABAP裡的外部類和內部類Java