Lambda 表達(dá)式的變量與作用域
本節(jié)我們將分析 Lambda 表達(dá)式的局部變量及其作用域進行分析,在這基礎(chǔ)上我們會探討其訪問規(guī)則背后的原因。
在開始之前我們需要明確一句話:
引用值,而不是變量!
引用值,而不是變量!
引用值,而不是變量!
重要的事情說三遍!??!
1. 訪問局部變量
Lambda 表達(dá)式不會從父類中繼承任何變量名,也不會引入一個新的作用域。Lambda 表達(dá)式基于詞法作用域,也就是說 Lambda 表達(dá)式函數(shù)體里面的變量和它外部環(huán)境的變量具有相同的語義。
訪問局部變量要注意如下 3 點:
- 可以直接在 Lambda 表達(dá)式中訪問外層的局部變量;
- 在 Lambda 表達(dá)式當(dāng)中被引用的變量的值不可以被更改;
- 在 Lambda 表達(dá)式當(dāng)中不允許聲明一個與局部變量同名的參數(shù)或者局部變量。
現(xiàn)在我們來仔細(xì)說明下這三點。
1.1 可以直接在 Lambda 表達(dá)式中訪問外層的局部變量
在 Lambda 表達(dá)式中可以直接訪問外層的局部變量,但是這個局部變量必須是聲明為 final
的。
首先我們來看一個例子:
import java.util.function.BinaryOperator;
public class LambdaTest1 {
public static void main(String[] args) {
final int delta = -1;
BinaryOperator<Integer> add = (x, y) -> x+y+delta;
Integer apply = add.apply(1, 2);//結(jié)果是2
System.out.println(apply);
}
}
在這個例子中, delta
是 Lambda 表達(dá)式中的外層局部變量,被聲明為 final
,我們的 Lambda 表達(dá)式是對兩個輸入?yún)?shù) x
,y
和外層局部變量 delta
進行求和。
如果這個變量是一個既成事實上的 final 變量的話,就可以不使用 final
關(guān)鍵字。所謂個既成事實上的 final 變量是指只能給變量賦值一次,在我們的第一個例子中,delta
只在初始化的時候被賦值,所以它是一個既成事實的 final
變量。
import java.util.function.BinaryOperator;
public class LambdaTest2 {
public static void main(String[] args) {
int delta = -1;
BinaryOperator<Integer> add = (x, y) -> x+y+delta;
Integer apply = add.apply(1, 2);//結(jié)果是2
System.out.println(apply);
}
}
相較于第一個例子,我們刪除了 final
關(guān)鍵字,程序沒有任何問題。
1.2 在 Lambda 表達(dá)式當(dāng)中被引用的變量的值不可以被更改
在 Lambda 表達(dá)式中試圖修改局部變量是不允許的,那么我們在后面對 delta
賦值會怎么樣呢?
public static void main(String...s){
int delta = -1;
BinaryOperator<Integer> add = (x, y) -> x+y+ delta; //編譯報錯
add.apply(1,2);
delta = 2;
}
這個時候編譯器會報錯說:
Variable used in lambda expression should be final or effectively final
1.3 在 Lambda 表達(dá)式當(dāng)中不允許聲明一個與局部變量同名的參數(shù)或者局部變量
public static void main(String...s){
int delta = -1;
BinaryOperator<Integer> add = (delta, y) -> delta + y + delta; //編譯報錯
add.apply(1,2);
}
我們將表達(dá)式的第一個參數(shù)的名稱由 x
改為 delta
時,編譯器會報錯說:
Variable 'delta' is already defined in the scope
2. 訪問對象字段與靜態(tài)變量
Lambda 內(nèi)部對于實例的字段和靜態(tài)變量是即可讀又可寫的。
import java.util.function.BinaryOperator;
public class Test {
public static int staticNum;
private int num;
public void doTest() {
BinaryOperator<Integer> add1 = (x, y) -> {
num = 3;
staticNum = 4;
return x + y + num + Test.staticNum;
};
Integer apply = add1.apply(1, 2);
System.out.println(apply);
}
public static void main(String[] args) {
new Test().doTest();
}
}
這里我們在 Test
類中,定義了一個靜態(tài)變量 staticNum
和 私有變量 num
。并在 Lambda 表達(dá)式 add1
中對其作了修改,沒有任何問題。
3. 關(guān)于引用值,而不是變量
通過前面兩節(jié)我們對于 Lambda 表達(dá)式的變量和作用域有了一個概念,總的來說就是:
Tips: Lambda 表達(dá)式可以讀寫實例變量,只能讀取局部變量。
有沒有想過這是為什么呢?
- 實例變量和局部變量的實現(xiàn)不同:實例變量都存儲在堆中,而局部變量則保存在棧上。如果在線程中要直接訪問一個非
final
局部變量,可能線程執(zhí)行時這個局部變量已經(jīng)被銷毀了。因此,Java 在訪問自由局部變量時,實際上是在訪問它的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什么區(qū)別了——因此就沒有這個限制(也就是既成事實的final
)。 - 這個局部變量的訪問限制也是 Java 為了促使你從命令式編程模式轉(zhuǎn)換到函數(shù)式編程模式,這樣會很容易使用 Java 做到并行處理(關(guān)于命令式編程模式和函數(shù)式編程模式我們將在后續(xù)內(nèi)容中做詳細(xì)的解釋)。
4. 小結(jié)

本節(jié)我們主要介紹了 Lambda 表達(dá)式的變量作用域,主要有這么 3 點需要記?。?/p>
- 引用值,而不是變量;
- 可以讀寫實例變量;
- 只能讀取局部變量。
最后我們對于 Lambda 表達(dá)式對于變量為什么會有這樣的訪問限制做了相應(yīng)的分析。