kotlin高阶函数与inline

高阶函数与Lambda

高阶函数就是将函数用作参数或者返回值的函数(可以理解为一种传递写法)

例子:

//函数作为参数

funtest1(f: () -> Unit){}

//函数作为返回值

funtest2(other1: Int, other2: Int): () -> Int = {other1 + other2}

kotlin的函数参数中,T.() ->Unit与() -> Unit的区别

前者是一个带接收者的函数类型,并且可以在函数内部调用改实例的成员,该接收者可以通过this访问。后者是一个没有接收者的高阶函数。

例子如下:

我们为SharedPreferences类型扩展两个函数,test1和test2

fun<T: SharedPreferences> T.test1(f: T.() -> Unit){}

fun<T: SharedPreferences> T.test2(f: () -> Unit){}

在进行调用的时候会出现:

总结:指定接收者类型的函数的字⾯值内部,传给调⽤的接收者对象成为隐式的this,以便访问接收者对象的成员⽽⽆需任何额外的限定符,亦可使⽤ this 表达式 访问接收者对象。

Lambda

fun SharedPreferences.edit(

commit: Boolean = false,

action: SharedPreferences.Editor.() -> Unit

) {

val editor = edit()

action(editor)

if (commit) {

editor.commit()

} else {

editor.apply()

}

}

private const val KEY_TOKEN = “token”

classPreferencesManager(privateval preferences: SharedPreferences){

funsaveToken(token: String) {

preferences.edit { putString(KEY_TOKEN, token) }

}

}

字节码反编译后的java代码:

//字节码反编译后

/* Copyright 2020 Google LLC.

SPDX-License-Identifier: Apache-2.0 */

publicfinalvoidsaveToken(@NotNull final String token){

// 我们定义的修改 SharedPreferences 的扩展方法被调用了

PreferenceManagerKt.edit$default(

this.preferences, // SharedPreferences 实例对象

false,// commit 标记的默认值

(Function1)(new Function1() { // 为 action 参数创建了新的 Function 对象

// $FF: synthetic method

// $FF: bridge method

public Object invoke(Object var1){

this.invoke((Editor)var1);

return Unit.INSTANCE;

}

publicfinalvoidinvoke(@NotNull Editor $this$edit){

Intrinsics.checkParameterIsNotNull($this$edit, "$receiver");

$this$edit.putString("token", token); // 我们 action 参数中的实现

}

}), 1, (Object)null);

}

每个高阶函数都会造成函数对象的创建和内存的分配,从而带来额外的运行时开销

内联函数inline

减少函数对象的创建

Lambda表达式中的内容在进行编译的时候会创建一个匿名内部类默认实现的函数对象

使用inline关键字减少函数对象的创建:

inlinefun SharedPreferences.edit(

commit: Boolean = false,

action: SharedPreferences.Editor.() -> Unit

) { … }

字节码反编译后的java代码:

//字节码反编译后

/* Copyright 2020 Google LLC.

SPDX-License-Identifier: Apache-2.0 */

publicfinalvoidsaveToken(@NotNull String token){

// SharedPreferences.edit 函数中的内容

SharedPreferences $this$edit$iv = this.preferences;

boolean commit$iv = false;

int $i$f$edit = false;

Editor editor$iv = $this$edit$iv.edit();

Intrinsics.checkExpressionValueIsNotNull(editor$iv, "editor");

int var7 = false;

// action 参数中实现的内容

editor$iv.putString("token", token);

// SharedPreferences.edit 函数中的内容

editor$iv.apply();

}

由于使用了 inline 关键字,编译器会将内联函数的内容复制到调用处,从而避免了创建新的函数对象。

####在哪些地方使用inline

试图标记没有接收函数作为参数的函数为内联函数,那么无法得到性能上的提升,因为内联函数的本质就是代码替代对象的创建来进行性能上的提升。因为inline会增加相应的代码生成量,因此要避免内联大型函数,kotlin标准库中的内联函数大部分都是1~3行。

第一种情况: 函数中有多个函数参数。

**第二种情况:**函数只接收一个函数作为参数,那么就干脆不要使用 inline。如果执意使用 inline 关键字,就必须将参数标记为 noinline,但是这么一来,内联此方法的性能优势微乎其微。

noinline

内联函数不能持有传入函数参数对象的引用,也不能将传入函数参数对象传递给另外一个函数。因此需要noinline关键字标记相应的函数对象,在编译时仍然将其实例化。

funmyFunction(importantAction: Int.() -> Unit) {

importantAction(-1)

}

inlinefun SharedPreferences.edit(

commit: Boolean = false,

//这里使用noinline关键字

noinline importantAction: Int.() -> Unit = { },

action: SharedPreferences.Editor.() -> Unit

) {

myFunction(importantAction)

...

}

...

funsaveToken(token: String) {

var dummy = 3

preferences.edit(importantAction = { dummy = this}) {

putString(KEY_TOKEN, token)

}

}

看看字节码反编译后的java代码:

/* Copyright 2020 Google LLC.

SPDX-License-Identifier: Apache-2.0 */

publicfinalvoidsaveToken(@NotNull String token){

// saveToken 方法中的功能

final IntRef x = new IntRef();

x.element = 3;

// 内联 edit 方法中的功能

SharedPreferences $this$edit$iv = this.preferences;

// noinline 函数声明导致 new Function 被调用

Function1 importantAction$iv = (Function1)(new Function1() {

// $FF: synthetic method

// $FF: bridge method

public Object invoke(Object var1){

this.invoke(((Number)var1).intValue());

return Unit.INSTANCE;

}

publicfinalvoidinvoke(int $receiver){

// saveToken 的功能

x.element = $receiver;

}

});

// 内联 edit 方法中的功能

boolean commit$iv = false;

int $i$f$edit = false;

PreferenceManagerKt.myFunction(importantAction$iv);

Editor editor$iv = $this$edit$iv.edit();

Intrinsics.checkExpressionValueIsNotNull(editor$iv, "editor");

int var9 = false;

editor$iv.putString("token", token);

editor$iv.apply();

}

可以看到添加了noinline关键字后,编译器使用了创建匿名内部类的形式来进行创建函数对象。而其他没有标记noinline的仍然会把内联函数复制到调用处。

高阶函数Lambda与inline的结合

高阶函数中每一个函数都是一个对象,并且会捕获一个闭包,即函数体内会访问到的外部作用域的变量。

lambda表达式会带来额外的内存分配,因此可以使用inline进行节约内存的开销。

参考引用

  • 内联函数中章节引用自谷歌开发者公众号,Kotlin Vocabulary | 内联函数的原理与应用

以上是 kotlin高阶函数与inline 的全部内容, 来源链接: utcz.com/a/28604.html

回到顶部