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

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