<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 閉包 在上一節的`ProduceFunction.java`中,我們從方法中返回 Lambda 函數。 雖然過程簡單,但是有些問題必須再回過頭來探討一下。 **閉包**(Closure)一詞總結了這些問題。 它非常重要,利用閉包可以輕松生成函數。 考慮一個更復雜的 Lambda,它使用函數作用域之外的變量。 返回該函數會發生什么? 也就是說,當你調用函數時,它對那些 “外部 ”變量引用了什么? 如果語言不能自動解決,那問題將變得非常棘手。 能夠解決這個問題的語言被稱為**支持閉包**,或者叫作在詞法上限定范圍( 也使用術語*變量捕獲*)。Java 8 提供了有限但合理的閉包支持,我們將用一些簡單的例子來研究它。 首先,下列方法返回一個函數,該函數訪問對象字段和方法參數: ~~~java // functional/Closure1.java import java.util.function.*; public class Closure1 { int i; IntSupplier makeFun(int x) { return () -> x + i++; } } ~~~ 但是,仔細考慮一下,`i`的這種用法并非是個大難題,因為對象很可能在你調用`makeFun()`之后就存在了——實際上,垃圾收集器幾乎肯定會保留以這種方式被綁定到現存函數的對象\[^5\]。當然,如果你對同一個對象多次調用`makeFun()`,你最終會得到多個函數,它們共享`i`的存儲空間: ~~~java // functional/SharedStorage.java import java.util.function.*; public class SharedStorage { public static void main(String[] args) { Closure1 c1 = new Closure1(); IntSupplier f1 = c1.makeFun(0); IntSupplier f2 = c1.makeFun(0); IntSupplier f3 = c1.makeFun(0); System.out.println(f1.getAsInt()); System.out.println(f2.getAsInt()); System.out.println(f3.getAsInt()); } } ~~~ 輸出結果: ~~~ 0 1 2 ~~~ 每次調用`getAsInt()`都會增加`i`,表明存儲是共享的。 如果`i`是`makeFun()`的局部變量怎么辦? 在正常情況下,當`makeFun()`完成時`i`就消失。 但它仍可以編譯: ~~~java // functional/Closure2.java import java.util.function.*; public class Closure2 { IntSupplier makeFun(int x) { int i = 0; return () -> x + i; } } ~~~ 由`makeFun()`返回的`IntSupplier`“關住了”`i`和`x`,因此即使`makeFun()`已執行完畢,當你調用返回的函數時`i`和`x`仍然有效,而不是像正常情況下那樣在`makeFun()`執行后`i`和`x`就消失了。 但請注意,我沒有像`Closure1.java`那樣遞增`i`,因為會產生編譯時錯誤。代碼示例: ~~~java // functional/Closure3.java // {WillNotCompile} import java.util.function.*; public class Closure3 { IntSupplier makeFun(int x) { int i = 0; // x++ 和 i++ 都會報錯: return () -> x++ + i++; } } ~~~ `x`和`i`的操作都犯了同樣的錯誤:被 Lambda 表達式引用的局部變量必須是`final`或者是等同`final`效果的。 如果使用`final`修飾`x`和`i`,就不能再遞增它們的值了。代碼示例: ~~~java // functional/Closure4.java import java.util.function.*; public class Closure4 { IntSupplier makeFun(final int x) { final int i = 0; return () -> x + i; } } ~~~ 那么為什么在`Closure2.java`中,`x`和`i`非`final`卻可以運行呢? 這就叫做**等同 final 效果**(Effectively Final)。這個術語是在 Java 8 才開始出現的,表示雖然沒有明確地聲明變量是`final`的,但是因變量值沒被改變過而實際有了`final`同等的效果。 如果局部變量的初始值永遠不會改變,那么它實際上就是`final`的。 如果`x`和`i`的值在方法中的其他位置發生改變(但不在返回的函數內部),則編譯器仍將視其為錯誤。每個遞增操作則會分別產生錯誤消息。代碼示例: ~~~java // functional/Closure5.java // {無法編譯成功} import java.util.function.*; public class Closure5 { IntSupplier makeFun(int x) { int i = 0; i++; x++; return () -> x + i; } } ~~~ **等同 final 效果**意味著可以在變量聲明前加上**final**關鍵字而不用更改任何其余代碼。 實際上它就是具備`final`效果的,只是沒有明確說明。 通過在閉包中使用`final`關鍵字提前修飾變量`x`和`i`, 我們解決了`Closure5.java`中的問題。代碼示例: ~~~java // functional/Closure6.java import java.util.function.*; public class Closure6 { IntSupplier makeFun(int x) { int i = 0; i++; x++; final int iFinal = i; final int xFinal = x; return () -> xFinal + iFinal; } } ~~~ 上例中`iFinal`和`xFinal`的值在賦值后并沒有改變過,因此在這里使用`final`是多余的。 如果函數式方法中使用的外部局部變量是引用,而不是基本類型的話,會是什么情況呢?我們可以把`int`類型改為`Integer`類型研究一下: ~~~java // functional/Closure7.java // {無法編譯成功} import java.util.function.*; public class Closure7 { IntSupplier makeFun(int x) { Integer i = 0; i = i + 1; return () -> x + i; } } ~~~ 編譯器非常聰明地識別到變量`i`的值被更改過。 因為包裝類型可能被特殊處理過了,所以我們嘗試下**List**: ~~~java // functional/Closure8.java import java.util.*; import java.util.function.*; public class Closure8 { Supplier<List<Integer>> makeFun() { final List<Integer> ai = new ArrayList<>(); ai.add(1); return () -> ai; } public static void main(String[] args) { Closure8 c7 = new Closure8(); List<Integer> l1 = c7.makeFun().get(), l2 = c7.makeFun().get(); System.out.println(l1); System.out.println(l2); l1.add(42); l2.add(96); System.out.println(l1); System.out.println(l2); } } ~~~ 輸出結果: ~~~ [1] [1] [1, 42] [1, 96] ~~~ 可以看到,這次一切正常。我們改變了**List**的內容卻沒產生編譯時錯誤。通過觀察本例的輸出結果,我們發現這看起來非常安全。這是因為每次調用`makeFun()`時,其實都會創建并返回一個全新而非共享的`ArrayList`。也就是說,每個閉包都有自己獨立的`ArrayList`,它們之間互不干擾。 請**注意**我已經聲明`ai`是`final`的了。盡管在這個例子中你可以去掉`final`并得到相同的結果(試試吧!)。 應用于對象引用的`final`關鍵字僅表示不會重新賦值引用。 它并不代表你不能修改對象本身。 下面我們來看看`Closure7.java`和`Closure8.java`之間的區別。我們看到:在`Closure7.java`中變量`i`有過重新賦值。 也許這就是觸發**等同 final 效果**錯誤消息的原因。 ~~~java // functional/Closure9.java // {無法編譯成功} import java.util.*; import java.util.function.*; public class Closure9 { Supplier<List<Integer>> makeFun() { List<Integer> ai = new ArrayList<>(); ai = new ArrayList<>(); // Reassignment return () -> ai; } } ~~~ 上例,重新賦值引用會觸發錯誤消息。如果只修改指向的對象則沒問題,只要沒有其他人獲得對該對象的引用(這意味著你有多個實體可以修改對象,此時事情會變得非常混亂),基本上就是安全的\[^6\]。 讓我們回顧一下`Closure1.java`。那么現在問題來了:為什么變量`i`被修改編譯器卻沒有報錯呢。 它既不是`final`的,也不是**等同 final 效果**的。因為`i`是外圍類的成員,所以這樣做肯定是安全的(除非你正在創建共享可變內存的多個函數)。是的,你可以辯稱在這種情況下不會發生變量捕獲(Variable Capture)。但可以肯定的是,`Closure3.java`的錯誤消息是專門針對局部變量的。因此,規則并非只是“在 Lambda 之外定義的任何變量必須是`final`的或**等同 final 效果**那么簡單。相反,你必須考慮捕獲的變量是否是**等同 final 效果**的。 如果它是對象中的字段,那么它擁有獨立的生存周期,并且不需要任何特殊的捕獲,以便稍后在調用 Lambda 時存在。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看