亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

增強的“for”循環和 lambda 表達式

增強的“for”循環和 lambda 表達式

料青山看我應如是 2022-05-21 20:25:57
據我了解,lambda 表達式捕獲的是值,而不是變量。例如,以下是編譯時錯誤:for (int k = 0; k < 10; k++) {    new Thread(() -> System.out.println(k)).start();    // Error—cannot capture k    // Local variable k defined in an enclosing scope must be final or effectively final   }但是,當我嘗試運行具有增強功能的相同邏輯時,for-loop一切正常:List<Integer> listOfInt = new Arrays.asList(1, 2, 3);for (Integer arg : listOfInt) {    new Thread(() -> System.out.println(arg)).start();    // OK to capture 'arg' }為什么它對于增強for循環而不是普通常規for循環工作正常,盡管增強for循環也在內部某處增加變量,如普通循環所做的那樣。**
查看完整描述

3 回答

?
翻過高山走不出你

TA貢獻1875條經驗 獲得超3個贊

Lambda 表達式的工作方式類似于回調。在將它們傳遞到代碼中的那一刻,它們“存儲”了它們操作所需的任何外部值(或引用)(就好像這些值在函數調用中作為參數傳遞一樣。這只是對開發人員隱藏)。k在您的第一個示例中,您可以通過存儲到單獨的變量(如 d)來解決此問題:


for (int k = 0; k < 10; k++) {

    final int d = k

    new Thread(() -> System.out.println(d)).start();

}

實際上final意味著,在上面的示例中,您可以省略“final”關鍵字,因為d它實際上是最終的,因為它在其范圍內從未更改。


for循環的操作方式不同。它們是迭代代碼(與回調相反)。它們在各自的范圍內工作,并且可以使用自己堆棧上的所有變量。這意味著,for循環的代碼塊是外部代碼塊的一部分。


至于您突出顯示的問題:


增強的for循環不能使用常規索引計數器操作,至少不能直接操作。增強for的循環(在非數組上)創建一個隱藏的迭代器。您可以通過以下方式對此進行測試:


Collection<String> mySet = new HashSet<>();

mySet.addAll(Arrays.asList("A", "B", "C"));

for (String myString : mySet) {

    if (myString.equals("B")) {

        mySet.remove(myString);

    }

}

上面的示例將導致 ConcurrentModificationException。這是由于迭代器注意到底層集合在執行期間發生了變化。但是,在您的示例中,外部循環創建了一個“有效的最終”變量arg,可以在 lambda 表達式中引用,因為該值是在執行時捕獲的。


防止捕獲“非有效最終”值或多或少只是 Java 中的一種預防措施,因為在其他語言(例如 JavaScript)中,這會有所不同。


所以編譯器理論上可以翻譯你的代碼,捕獲值,然后繼續,但它必須以不同的方式存儲那個值,你可能會得到意想不到的結果。因此,為 Java 8 開發 lambdas 的團隊正確地排除了這種情況,通過異常阻止它。


如果您需要更改 lambda 表達式中的外部變量的值,您可以聲明一個單元素數組:


String[] myStringRef = { "before" };

someCallingMethod(() -> myStringRef[0] = "after" );

System.out.println(myStringRef[0]);

或使用AtomicReference<T>使其成為線程安全的。但是,對于您的示例,這可能會返回“之前”,因為回調很可能在 println 執行之后執行。


查看完整回答
反對 回復 2022-05-21
?
慕工程0101907

TA貢獻1887條經驗 獲得超5個贊

在增強的 for 循環中,每次迭代都會初始化變量。來自Java 語言規范(JLS)的§14.14.2 :


...


當執行增強for語句時,局部變量在循環的每次迭代中被初始化為數組的連續元素或Iterable由表達式產生。增強語句的確切含義for通過翻譯成基本for語句給出,如下:


如果Expression的類型是 的子類型Iterable,則翻譯如下。


如果Expression的類型是Iterable<X>某個類型參數的子類型X,則令I為類型java.util.Iterator<X>;否則,I設為原始類型java.util.Iterator。


增強for語句等價于for以下形式的基本語句:


for (I #i = Expression.iterator(); #i.hasNext(); ) {

    {VariableModifier} TargetType Identifier =

        (TargetType) #i.next();

    Statement

}

...


否則,表達式必須具有數組類型,T[]。


讓L1 ... Lm是緊接在增強for語句之前的(可能為空的)標簽序列。


增強for語句等價于for以下形式的基本語句:


T[] #a = Expression;

L1: L2: ... Lm:

for (int #i = 0; #i < #a.length; #i++) {

    {VariableModifier} TargetType Identifier = #a[#i];

    Statement

}

...


換句話說,您的增強 for 循環等效于:


ArrayList<Integer> listOfInt = new ArrayList<>();

// add elements...


for (Iterator<Integer> itr = listOfInt.iterator(); itr.hasNext(); ) {

    Integer arg = itr.next();

    new Thread(() -> System.out.println(arg)).start();

}

由于每次迭代都會初始化變量,因此它實際上是最終的(除非您在循環內修改變量)。


相反,基本 for 循環中的變量(k在您的情況下)被初始化一次并在每次迭代時更新(如果存在“ ForUpdate ”,例如k++)。有關詳細信息,請參閱JLS 的§14.14.1。由于變量更新,每次迭代都不是最終的,也不是有效的最終。


JLS 的§15.27.2規定并解釋了對最終或有效最終變量的需求:


...


任何使用但未在 lambda 表達式中聲明的局部變量、形式參數或異常參數都必須聲明final或有效地最終確定(第 4.12.4 節),否則在嘗試使用時會發生編譯時錯誤。


任何使用但未在 lambda 主體中聲明的局部變量必須在 lambda 主體之前明確分配(第 16 節(Definite Assignment)),否則會發生編譯時錯誤。


變量使用的類似規則適用于內部類的主體(第 8.1.3 節)。對有效最終變量的限制禁止訪問動態變化的局部變量,這些變量的捕獲可能會引入并發問題。與final限制相比,它減輕了程序員的文書負擔。


對有效最終變量的限制包括標準循環變量,但不包括增強for循環變量,它們對于循環的每次迭代都被視為不同的(第 14.14.2 節)。


...


最后一句話甚至明確提到了基本 for 循環變量和增強型 for 循環變量之間的區別。


查看完整回答
反對 回復 2022-05-21
?
慕容森

TA貢獻1853條經驗 獲得超18個贊

其他回復很有幫助,但他們似乎沒有直接解決問題并明確回答。


在您的第一個示例中,您嘗試k從 lambda 表達式進行訪問。這里的問題是k隨著時間的推移改變它的值(k++在每次循環迭代之后調用)。Lambda 表達式確實捕獲了外部引用,但它們需要被標記為final或“有效地最終”(即,將它們標記為final仍會產生有效代碼)。這是為了防止并發問題;在您創建的線程運行時,k可能已經擁有一個新值。


另一方面,在您的第二個示例中,您正在訪問的變量是arg,它會在增強的 for 循環的每次迭代中重新初始化(與上面的示例相比,k僅更新),因此您正在創建一個完全每次迭代的新變量。順便說一句,您還可以將增強型 for 循環的迭代變量顯式聲明為final:


for (final Integer arg : listOfInt) {

    new Thread(() -> System.out.println(arg)).start();

}

這可確保在arg您創建的線程運行時值引用不會更改。


查看完整回答
反對 回復 2022-05-21
  • 3 回答
  • 0 關注
  • 399 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號