本系列第三篇,聚焦 Lambda 語法結構、與匿名內部類的語法/語義差異、作用域規則(this/super、變量遮蔽)、變量捕獲限制、遞歸 Lambda 寫法,附對比代碼與編譯器生成字節碼視角解析。
參數列表 -> Lambda 體
// 形式:
() -> "Hello" // 無參,表達式體
x -> x * 2 // 單參,省略括號
(int x, int y) -> x + y // 多參,建議顯式類型
(String s) -> { return s.length(); } // 塊體,需 return
(x, y) -> { System.out.println(x); return x+y; } // 多語句
-> 分隔參數與體,非運算符。return 或 void 兼容。throws 聲明,檢查型異常需在函數式接口方法簽名聲明。| 維度 | 匿名內部類 | Lambda 表達式 |
|---|---|---|
| 語法 | 冗長:new Interface() { ... } | 極簡:參數 -> 體 |
| 標識 | 每個實例有唯一 this/hashCode | 無標識性,行為即值,可能共享實例 |
| 作用域 | 可直接訪問外部類字段/方法名 | 不繼承父類型成員,需 InterfaceName.field 訪問 |
| this/super | 指向匿名類實例,外部類用 Outer.this | 與外層語境共享,指向外層實例 |
interface LetsRun extends Runnable {
String CONST = "Outer";
}
new Thread(() -> System.out.println(LetsRun.CONST)).start(); // 必須加接口名
// new Thread(() -> System.out.println(CONST)).start(); // 編譯錯誤
class Foo {
String x = "field";
BinaryOperator op = (x, y) -> x + y; // 參數 x 遮蔽字段 x
}
class Super { String s = "Super"; }
class Sub extends Super { String s = "Sub"; void test() {
Runnable r = () -> { System.out.println(this.s); System.out.println(super.s); };
r.run(); // 輸出 Sub / Super
}}
final,Java 8+ 放寬為 effectively final。String s1 = "hello"; // effectively final
final String s2 = "world"; // 顯式 final
// s1 = "changed"; // 取消註釋則編譯錯誤:Variable used in lambda expression should be final or effectively final
Runnable r = () -> System.out.println(s1 + s2); // 合法
Lambda 無 this 指向自身,無法直接遞歸。通過「先聲明字段,再賦值」繞過:
IntUnaryOperator fact = null; // 必須先聲明
fact = n -> n == 0 ? 1 : n * fact.applyAsInt(n - 1);
System.out.println(fact.applyAsInt(5)); // 120
原理:fact 捕獲的是字段引用,賦值後字段指向 Lambda 實例,內部調用 fact.applyAsInt 即間接遞歸。
invokedynamic 指令,運行時由 LambdaMetafactory 動態生成實現類 (如 Foo$$Lambda$1)。ConstantCallSite)。AtomicReference。stream().map().filter()...) 導致除錯困難,適度提取中間變量。Lambda 不僅是語法糖,更帶來了「行為參數化」的編程範式轉變。掌握其作用域規則(不繼承接口成員、this 語義外層一致)、變量捕獲限制、遞歸寫法,能寫出簡潔、線程安全、易並行的函數式代碼。下一篇將探討方法引用、構造器引用、預定義函數式接口 (java.util.function) 實戰應用。