1.lambda类型解释
在kotlin中,lambda可以表示函数类型,也可以表示Java语言声明的函数式接口,但用kotlin语言声明的函数式接口是不能用lambda表示的,kotlin声明的函数式接口和lambda是不能互相转换的。
lambda表示函数:
val addMethod: (Int, Int) -> Int = { a, b ->
a + b
}
lambda表示java声明的函数式接口:
val runnable: Runnable = Runnable { println("lambda代表Java函数式接口") }
2.lambda return 工作流
kotlin中lambda表达式默认最后一行表达式就是返回值,不能显式的return。其他的普通方法需要显示的return。只有当lambda作为内联函数的参数的时候,才可以显示的return,因为这时lambda的代码会被插入到引用内联函数的函数的代码体中,此时return的类型是引用这个内联函数的函数的返回类型。
非内联的lambda内是不允许显示return的, 但是可以使用标签局部返回
3.内联lambda
默认情况下,kotlin中每个lambda表达式都会被编译成一个匿名类,这对性能是有一定损耗的。如果把 lambda 传给了标记成 inline 的 Kotlin 函数,这时由于lambda的代码会被插入到内联函数中,这时就不会在额外创建匿名类了。
4.lambda捕捉方法中的局部变量
4.1 Java是如何在匿名内部类或者lambda表达式中捕获外部方法的局部变量?
先看一段再熟悉不过的Java代码:
public void main() {
//如果变量被内部类捕获,需要声明为final
final int a = 0;
new Runnable() {
@Override
public void run() {
//内部类只能使用变量a,但是不能修改
int b = a;
//a++; 报错
}
};
}
java7之前,匿名内部类想要使用外部的临时变量,必须是final的,不能修改。java8之后,引入了Lambda,并且在匿名内部类和Lambda中使用外部临时变量时,如果只是使用可以不加final。但是同样不能修改
4.2 java中匿名内部类和Lambda不能修改外部的临时变量的原因
原因1:生命周期问题,外部变量的声明周期是方法栈,匿名内部类和Lambda则不是
原因2:副本。匿名内部类编译会生成一个新的类,保留外部类的引用以及临时变量的副本,所以在内名内部类中修改外部的临时变量实际上是修改的副本,会造成歧义,所以java直接禁止在匿名内部类和Lambda中修改外部临时变量
4.3 如果就想在java的匿名内部类中修改外部变量怎么办?
两种技巧:要么声明一个单元素的教组,其中存储可变值;要么创建一个包装类的实例,其中存储要改变的值的引用。因为不管是数组还是包装类,他们都是引用类型,这个样当这个引用类型被捕获时,虽然他是final不可变的,但是它内部维护的数据是可变的。
4.3 kotlin是如何在lambda表达式中捕获外部方法的局部变量?
还是先看一段kotlin代码:
fun main() {
//a 不必声明为final
var a = 0
Runnable {
//可以直接使用a
var b = a
//也可以直接修改a
a++
}
}
kotlin的lambda是可以使用外部变量的,这一点和Java是相同的,但是神奇的是kotlin允许修改外部临时变量。上边说过Java中声明的int 类型的变量 a 是不允许修改的,因为在Java中int是基本类型,在匿名内部类中基本类型保存的是副本,所以对副本的修改并不能影响原来的外部变量。而kotlin却可以修改外部变量,原因如下:
他的原理就是会自动包裹一层引用。对于基本类型,kotlin提供了 IntRef、BooleanRef等引用类型来包裹基本类型,对于引用类型,kotlin提供了 ObjectRef来包裹,从而达到在lambda内部修改外部的变量
package kotlin.jvm.internal;
import java.io.Serializable;
public class Ref {
private Ref() {}
public static final class ObjectRef<T> implements Serializable {
public T element;
@Override
public String toString() {
return String.valueOf(element);
}
}
public static final class IntRef implements Serializable {
public int element;
@Override
public String toString() {
return String.valueOf(element);
}
}
看一个kotlin在lambda中修改外部变量的例子:
fun test() {
var a = 1
run(Runnable {
a = 2
})
println("a:$a")
}
private fun run(runnable: Runnable) {
runnable.run()
}
反编译为Java后的代码:
public final void test() {
final IntRef a = new IntRef();
a.element = 1;
this.run((Runnable)(new Runnable() {
public final void run() {
a.element = 2;
}
}));
String var2 = "a:" + a.element;
boolean var3 = false;
System.out.println(var2);
}
private final void run(Runnable runnable) {
runnable.run();
}
可见,kotlin对Int类型直接做了包装,这样就可以在匿名类中修改外部变量了。