日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

无状态编程, lambda 表达式中传入的局部变量,为什么需要是不可变的(final)

發布時間:2024/3/13 编程问答 67 豆豆
生活随笔 收集整理的這篇文章主要介紹了 无状态编程, lambda 表达式中传入的局部变量,为什么需要是不可变的(final) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

無狀態編程

說明

  • @author JellyfishMIX - github / blog.jellyfishmix.com
  • LICENSE GPL-2.0
  • 前言

    本文將會根據以下順序進行敘述:

    • lambda 表達式中傳入的局部變量,為什么需要是不可變的(final)?

    • 函數式編程提倡的無狀態。

    • 無狀態服務。

    lambda 表達式中傳入的局部變量,為什么需要是不可變的(final)?

    場景演示

    public class Demo {public static void main(String[] args) {int i = 0;i = 2;Thread thread = new Thread(() -> {System.out.println(i);});} }

    這里聲明了一個變量 i,初值為 0,然后對 i 的值做了修改,這讓 i 成為一個真正意義上的變量。于是 lambda 表達式在編譯時無法通過,報錯 “lambda 表達式中的變量 i 應該是常量,或效果上與常量相當的”。

    lambda 表達式中傳入的局部變量,為什么需要是不可變的(final)?

    原因

    lambda 表達式多作為回調函數來使用,是延遲計算的。當回調函數真正被觸發時,外部傳入回調函數的局部變量可能已經被改變,這違背了使用者的預期。jdk 在編譯環節就否定了外部傳入 lambda 表達式一個變量,只能是常量(即 final 修飾),或效果上與常量相當的(聲明賦初值后就沒有被修改過的變量),在編譯環節禁止了這種風險。

    解決方案

    public class Demo {public static void main(String[] args) {final int i = 0;Thread thread = new Thread(() -> {System.out.println(i);});int j = 1;Thread thread1 = new Thread(() -> {System.out.println(j);});} }

    像這樣,我們聲明一個常量 i,和一個效果上與常量相當的(聲明賦初值后就沒有被修改過的變量)j。i 和 j 都滿足 lambda 表達式對外部傳入的局部變量的要求,編譯可以通過。

    lambda 表達式語法糖,編譯后的樣子

    我們來看一下截圖中的 lambda 表達式,編譯后長什么樣?

    # 這是編譯用的命令 javac Demo.java # 這是反編譯用的命令 javap -p Demo.class

    public class lambda.concurrent.map.Demo {public lambda.concurrent.map.Demo();public static void main(java.lang.String[]);private static void lambda$main$1(int);private static void lambda$main$0();

    可以看到,.java 文件的 thread1 中的 lambda 表達式,被編譯成了當前類中一個叫 lambda$main$1(int); 的私有方法,原先 lambda 表達式中出現的外部局部變量,變成了此私有方法的入參。

    根據實驗得出結論:

  • lambda 表達式是一個語法糖,在編譯后會生成一個當前類的私有方法。
  • lambda 表達式內直接引用局部變量,本質是一種隱式傳參,編譯時會自動將引用的局部變量,放到根據 lambda 表達式生成的私有方法的參數列表中。
  • javac 編譯器中的"常量折疊"現象

    還有一點疑惑,.java 文件的 thread 中的 lambda 表達式,引用了一個常量。這個常量,為什么沒體現在根據 lambda 表達式生成的私有方法 lambda$main$0(); 的參數列表中呢?

    我們來看一下 IDEA 為我們反編譯出的更直觀的 .class 文件:

    public class Demo {public Demo() {}public static void main(String[] args) {int i = false;new Thread(() -> {// 這行注釋是我自己加的。請注意,常量 0,編譯后直接替換掉了原先的變量符號System.out.println(0);});int j = 1;new Thread(() -> {System.out.println(j);});} }

    我們可以看到,之前的常量 0,編譯后直接替換掉了原先的變量符號。這是 javac 編譯器的一種叫 “常量折疊” 的現象,可以在編譯時完成對常量的計算工作,使 JVM 在運行字節碼時更快速。

    函數式編程提倡的無狀態

    函數式編程提倡的無狀態是什么?

    lambda 表達式是函數式編程的思想,而函數式編程是提倡無狀態的,無狀態是指什么呢?

    復述一段引用:夏梓耀 - 知乎

    一般所說的狀態可視為 <reference, store, value> 這樣的三元組(引用,存儲,值),reference 也可以叫 pointer,store 可看做是一個接受 reference 返回 value 的容器(具體實現可以是內存單元),value就是存儲的值了;

    狀態變化是指兩方面變化了:1. reference 改變,2. reference 所指向的 value 改變。

    函數式編程提倡的無狀態是指,“進去過的東西不因進去過而改變”,可以理解為不要在函數內修改由外部提供的變量的狀態。

    無狀態編程的優勢?

    關于無狀態編程的優勢,可以看一下 stackOverflow 上的討論: Advantages of stateless programming

    總結一下自己閱讀討論后的理解:

  • 無狀態編程在并發編程場景下優勢明顯,可變狀態是多并發編程的天敵,會引發并發編程常見的竟態問題。如果默認情況下值是不可變的,開發者就無需擔心某個線程會改變多個線程之間共享變量的值,因此不可變的值消除了竟態條件。由于沒有竟態條件,所以不需要使用鎖,因此不可變性也消除了與死鎖相關的問題。
  • 純函數更容易測試和調試。
  • 比較簡單的無狀態實現方式,final 修飾變量,把變量標記為不可變。比如 String 類的設計,value 屬性加了 fianl 修飾,這讓 String 天然就是線程安全的。

    無狀態服務

  • 無狀態就是指不能把例如用戶登錄信息這種數據,放在服務集群中的某臺實例機器上,這樣其他機器是感知不到這個數據的,只有這臺機器自己能感知到。
  • 用戶的不同操作,都會給這個服務集群發請求,到底哪臺機器處理這個用戶的本次請求,是不確定的。
  • 所以用戶登錄信息,必須得放在一個服務集群中任何一臺機器都能感知到的地方,比如集中存儲到 redis 中。
  • 這樣就把"用戶登錄信息"這個狀態,從 單臺機器 移動到了 集中式存儲數據源,單臺機器就是無狀態的了。
  • 一個服務集群中,每臺機器運行的是同樣的代碼,此時稱這個服務為無狀態服務。
  • 總結

    以上是生活随笔為你收集整理的无状态编程, lambda 表达式中传入的局部变量,为什么需要是不可变的(final)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。