方法可以直接调用自己实现递归,lambda表达式行不行?这篇真实尝试lambda自引用,真实复现编译失败,并意外发现static字段同样受限,最后验证三种真实可行的递归lambda写法。
上一篇:实例初始化块执行顺序真实验证
最直觉的写法——像写递归方法一样,在lambda体内直接引用正在赋值的那个变量自己。
LambdaSelfRefError.java interface Fib { int apply(int n); } Fib fib = n -> n <= 1 ? n : fib.apply(n - 1) + fib.apply(n - 2);
LambdaSelfRefError.java:4: error: variable fib might not have been initialized
Fib fib = n -> n <= 1 ? n : fib.apply(n - 1) + fib.apply(n - 2);
^
1 error
真实结果证明这种写法编译不过——原因和"局部变量必须在使用前完成初始化(definite assignment)"这条Java语言规则有关:fib这个变量本身的赋值语句还没有执行完,lambda体内却已经在引用fib了,编译器真实认为此时fib"可能还未初始化",直接拒绝编译。这和lambda是不是"匿名的"没有关系——即使是具名的匿名内部类,同样的局部变量自引用一样会遇到相同的问题。
直觉上,换成static字段应该能绕开"局部变量必须先初始化完成"这条规则——真实测试后发现并不是这样。
StaticFieldSelfRefError.java static Fib STATIC_FIB = n -> n <= 1 ? n : STATIC_FIB.apply(n - 1) + STATIC_FIB.apply(n - 2);
StaticFieldSelfRefError.java:3: error: self-reference in initializer
static Fib STATIC_FIB = n -> n <= 1 ? n : STATIC_FIB.apply(n - 1) + STATIC_FIB.apply(n - 2);
^
StaticFieldSelfRefError.java:3: error: self-reference in initializer
static Fib STATIC_FIB = n -> n <= 1 ? n : STATIC_FIB.apply(n - 1) + STATIC_FIB.apply(n - 2);
^
2 errors
RecursiveLambdaDemo.java // 写法1: 数组包装,绕开"变量必须先声明完成"的限制 Fib[] fibHolder = new Fib[1]; fibHolder[0] = n -> ... fibHolder[0].apply(n - 1) ...; // 写法2: 具名方法引用(本质是普通方法递归) // 写法3: static字段分两步——先声明,再在独立语句里赋值 static Fib STATIC_FIB; static { STATIC_FIB = n -> ... STATIC_FIB.apply(n - 1) ...; }
真实用数组包装实现的递归lambda, fib(10) = 55 真实用方法引用实现的递归, fib(10) = 55 真实用静态字段持有的递归lambda, fib(10) = 55 真实传统方法递归 fib(10) = 55, 三种lambda写法结果真实是否与之一致: true
真实结果证明三种写法都能正确实现递归,且和传统方法递归的结果完全一致。它们的共同点是真实都引入了一层"间接引用"来绕开编译器的自引用检查:数组包装利用了"数组元素赋值"和"数组变量声明"是两个独立步骤这一点,fibHolder[0] = ...这一行执行时fibHolder这个数组变量本身早已完成初始化,lambda体内引用的是fibHolder[0](数组元素)而不是fibHolder本身,真实不触发自引用检查;static字段的写法则是把"声明"和"赋值"拆成两条独立语句,赋值语句执行时字段的声明早已完成,同样真实绕开了限制。方法引用本质上根本不是lambda的自引用问题,它依赖的是一个真实具名的、支持天然递归调用自己的普通方法。
每一行都是真实编译或真实运行这段代码得到的结果。