无状态编程, lambda 表达式中传入的局部变量,为什么需要是不可变的(final)
無狀態編程
說明
前言
本文將會根據以下順序進行敘述:
-  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 表達式中出現的外部局部變量,變成了此私有方法的入參。
根據實驗得出結論:
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 天然就是線程安全的。
無狀態服務
總結
以上是生活随笔為你收集整理的无状态编程, lambda 表达式中传入的局部变量,为什么需要是不可变的(final)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: VC小知识!
- 下一篇: Python实现将一张图片放到另一张图片
