Kotlin 中 Lambda的使用 四、内联函数 inline
一、内联函数
用关键词inline修饰的函数,称为内联函数。inline修饰的是用函数作为参数的高阶函数,如果修饰普通函数为警告 Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types,普通函数内联对性能的影响微不足道。
二、用inline修饰高阶函数的好处
我们在kotlin中对高阶函数分别使用普通函数声明和使用内联函数声明然后转成java代码然后进行比较。kotlin代码:
class Test {
//不使用inline 修饰的高阶函数
fun test(f: () -> Unit) {
f()
}
//使用inline 修饰的高阶函数
inline fun testInline(f: () -> Unit) {
f()
}
fun call() {
test { print("test") }
testInline { print("testInline") }
}
}
java代码:
public final class Test {
public final void test(@NotNull Function0 f) {
Intrinsics.checkNotNullParameter(f, "f");
f.invoke();
}
public final void testInline(@NotNull Function0 f) {
int $i$f$testInline = 0;
Intrinsics.checkNotNullParameter(f, "f");
f.invoke();
}
public final void call() {
this.test((Function0)null.INSTANCE);
int $i$f$testInline = false;
int var3 = false;
String var4 = "testInline";
boolean var5 = false;
System.out.print(var4);
}
}
在call中可以看出在调用非内联函数时是直接调用了此函数,并且创建了匿名类Function0用于Lambda函数的调用。内联函数则是复制了函数体过来,而没有创建匿名类,而是直接嵌入了Lambda
函数的实现体。
当高阶函数没有使用Inline
修饰时,调用此函数会直接引用此函数,并且会创建匿名类以实现此函数参数的调用,这有两部分开销,直接调用此函数会创建额外的栈帧以及入栈出栈操作(一个函数的调用就是一个栈帧入栈和出栈的过程),并且匿名类的创建也会消耗性能。使用 Inline 修饰高阶函数是会有性能上的提升
在调用内联函数时,基于上述我们已经知道内联会将Lambda
参数函数体直接进行嵌入,内联可能导致生成的代码增加;不过如果我们使用得当(即避免内联过大函数),性能上会有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。
三、禁用内联 noinline
如果希望只内联一部分传给内联函数的 lambda 表达式参数,那么可以用 noinline
修饰符标记不希望内联的函数参数:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { //…… }
可以内联的 lambda 表达式只能在内联函数内部调用或者作为可内联函数的参数传递。当作为非内联函数的参数传递或者存储到字段中的时候需要noinline修饰。
inline fun testInline(f1: () -> Unit, f: () -> Unit) {
f1.invoke()
val a = f//会提示错误,内联Lambad 不能参数赋值
testInlineInner(f) //会提示错误,内联Lambad 不能作为普通函数参数进行传递
}
fun testInlineInner(f: () -> Unit) {
f()
}
inline fun testInline(f1: () -> Unit, noinline f: () -> Unit) {
f1.invoke()
val a = f//可以
testInlineInner(f) //可以
}
四、在内联函数中的lambda表达式中使用return(非局部返回)
在 Kotlin 中,我们只能对具名或匿名函数使用正常的、非限定的 return 来退出。 退出一个 lambda 表达式,我们必须使用一个标签, lambda 表达式内部禁止使用裸 return
,因为 lambda 表达式不能使包含它的函数返回。
fun testInlineInner(f: () -> Unit) {
f()
}
testInlineInner {
return//'return' is not allowed here
}
但是如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它是允许的 ,这种返回(位于 lambda 表达式中,但退出包含它的函数)称为非局部返回。
inline fun testInlineInner(f: () -> Unit) {
f()
}
fun main(args: Array<String>) {
testInlineInner {
return// 可以,退出main函数
}
}
五、crossinline
当内联Lambda
不是直接在函数体中调用,而是在嵌套函数或者其他执行环境中调用则需要声明为crossinline。
inline fun f(crossinline body: () -> Unit) {
val f = Runnable { body() }
}
六、
Reified 类型的参数
reified 是Kotlin关于泛型的关键字,从而达到泛型不被擦除,如果需要使用reified 去修饰泛型方法中的泛型类型,则需要使用Inline修饰此泛型方法,因为Inline函数可以指定泛型类型不被擦除,因为内联函数编译期会将字节码嵌入到调用它的地方,所以编译器才会知道泛型对应的具体类型,使用reified 和 Inline适用于泛型方法,并且方法体中需要对泛型类型做以判断的情况。
inline fun <reified T> Bundle.plus(key: String, value: T) {
when (value) {
is String -> putString(key, value)
is Long -> putLong(key, value)
is Int -> putInt(key, value)
}
}
七、内联属性
inline
也可以修饰set
,get
方法,并且可以直接修饰属性使两个访问器都标记为inline。
class Amount(var amount: Long) {
private val isEmpty: Boolean
inline get() {
return amount <= 0
}
private val isEmptyNoInline: Boolean
get() {
return amount <= 0
}
fun test(){
val amount = Amount(10)
val isEmpty = amount.isEmpty
val isEmptyNoInline = amount.isEmptyNoInline
}
//转化为Java代码
public final void test() {
Amount amount = new Amount(10L);
int $i$f$isEmpty = false;
boolean isEmpty = amount.getAmount() <= 0L; //代码嵌入
boolean isEmptyNoInline = amount.isEmptyNoInline(); //非嵌入
}
}