5. 内联函数
inline
函数在调用它的地方,会把这个函数方法体中的所以代码移动到调用的地方,而不是通过方法间压栈进栈的方式。一定程度上可以代码效率。
比如如下的代码:
class TestInline { fun test() { highLevelFunction("Android") { it.length } } private fun highLevelFunction(input: String, mapper: (String) -> Int): Int { Log.d("TestInline", "highLevelFunction input:$input") return mapper(input) } }
highLevelFunction 函数没有添加 inline 的话,反编译之后可以看到 test 函数调用 highLevelFunction 的时候传入了 Function1 接口实例。
public final class TestInline { public final void test() { this.highLevelFunction("Android", (Function1) ...); } private final int highLevelFunction(String input, Function1 mapper) { Log.d("TestInline", Intrinsics.stringPlus("highLevelFunction input:", input)); return ((Number)mapper.invoke(input)).intValue(); } }
当添加了 inline 修饰再看下反编译的代码,会发现 highLevelFunction 函数的内容被编译进了 test 函数内。
public final class TestInline { public final void test() { String input$iv = "Android"; int $i$f$highLevelFunction = false; Log.d("TestInline", "highLevelFunction input:" + input$iv); int var5 = false; input$iv.length(); } }
但并非所有的函数都适合 inline 标注,强行标注的话会收到 IDE 的警告:
Expected performance impact of inlining ‘…’ can be insignificant. Inlining works best for functions with lambda parameters.
是否使用 inline 函数,可以简单参考如下:
不带参数,或是带有普通参数的函数,不建议使用 inline
带有 lambda 函数参数的函数,建议使用 inline
另外,inline 还可以让函数参数里面的 return 生效。因为平常的高阶函数调用传入方法体不允许 return,但如果该高阶函数标注了 inline 就可以直接 return 整个外部函数。
class TestInline { fun test() { highLevelFunction("Android") { it.length return // 可以 return 整个 test 函数,后续的 Log 不再输出 } Log.d("TestInline", "tested") } private inline fun highLevelFunction(input: String, mapper: (String) -> Int): Int { ... } }
反编译之后会发现,由于 return 的存在,后续的 Log 代码压根没参与编译。
6. 闭包
前面提到的 lambda 表达式或匿名函数可以访问其闭包,即便是作用域以外的局部变量,甚至可以进行修改。
比如下面的 stringMapper 的 lambda 参数内可以直接访问和修改外部的 sum 变量。
fun test() { var sum = 0 stringMapper("Android") { sum += it.length ... } print(sum) }
反编译后可以看到传入 stringMapper 高阶函数的是 Function1 接口的实现即匿名内部类,匿名内部类拷贝 sum 引用进行数值的修改操作。
public final void test() { final IntRef sum = new IntRef(); sum.element = 0; this.stringMapper("Android", (Function1)(new Function1() { public Object invoke(Object var1) { return this.invoke((String)var1); } public final int invoke(@NotNull String it) { Intrinsics.checkNotNullParameter(it, "it"); IntRef var10000 = sum; var10000.element += it.length(); return it.length(); } })); int var3 = sum.element; System.out.print(var3); }
如果高阶函数同时也是内联函数,那么实现较为直接,即在外部函数内直接操作变量即可。
public final void test() { int sum = 0; String input$iv = "Android"; int $i$f$stringMapper = false; int var7 = false; int sum = sum + input$iv.length(); input$iv.length(); System.out.print(sum); }
7. 顶层函数
Kotlin 允许在文件内直接定义函数,这个方法可以被称为顶层函数
。
// Test.kt fun topFunction(string: String) { println("this is top function for $string") }
这种函数可以在 Kotlin 中被直接调用,无需指定其实例或类名。
class TestInline { fun test() { ... topFunction("Ellison") } }
在 Java 中调用该顶层函数的话是和扩展函数一样的形式:
public class TestJava { static void main(String[] args) { TestKt.topFunction("Ellison"); } }
通过反编译会发现,原理跟扩展函数一样,其通过静态函数实现:
public final class TestKt { ... public static final void topFunction(@NotNull String string) { Intrinsics.checkNotNullParameter(string, "string"); String var1 = "this is top function for " + string; System.out.println(var1); } }
调用处反编译的实现也可想而知。
public final class TestInline { public final void test() { TestKt.topFunction("Ellison"); } }
留意下和扩展函数的区别:
- 定义的时候无需指定目标类名
- 调用的时候自然无需指定实例
8. 局部函数
除了允许在文件顶层定义函数外,Kotlin 还允许在现有函数内定义嵌套函数,称为局部函数
。
而且该函数还可以访问闭包。
fun magic(): Int { val v1 = (0..100).random() fun foo(): Int { return v1 * v1 } return foo() }
通过反编译发现和闭包是一样的实现:
public final int magic() { byte var2 = 0; IntRange var3 = new IntRange(var2, 100); final int v1 = RangesKt.random(var3, (Random)Random.Default); <undefinedtype> $fun$foo$1 = new Function0() { public Object invoke() { return this.invoke(); } public final int invoke() { return v1 * v1; } }; return $fun$foo$1.invoke(); }
当然如果没有访问闭包的话。
fun magic(): Int { val v1 = (0..100).random() fun foo(v: Int): Int { return v * v } return foo(v1) }
实现稍稍区别:
public final int magic() { byte var2 = 0; IntRange var3 = new IntRange(var2, 100); int v1 = RangesKt.random(var3, (Random)Random.Default); <undefinedtype> $fun$foo$1 = null.INSTANCE; return $fun$foo$1.invoke(v1); }
9. 运算符重载函数
Kotlin 允许使用 operator
关键字对已有函数进行重载,达到扩展函数参数、改写函数逻辑等目的。
比如如下给 Int
类型的 minus
方法扩展了支持 Person 参数的重载函数,这样的话数字即可与 Person 类型直接进行 -
号运算。
data class Person(var name: String, var age: Int) operator fun Int.minus(p: Person) = this - p.age fun testOperator() { val person1 = Person("A", 3) println("testInt+person1=${5 - person1}") }
如果不添加 operator 运算符的话,上述 5 - person1 的写法会无法通过编译,因为 - 运算符不识别 Person 类型。因为这种写法就变成了向 Int 类添加了接收 Person 参数的 minus 方法而已,使用的话就得要改成对象调用函数的形式:
... fun Int.minus(p: Person) = this - p.age fun testOperator() { val person1 = Person("A", 3) println("testInt+person1=${5.minus(person1)}") }
除此之外还可以重载 get()
、compareTo()
等函数,在这里还想额外谈下运算符重载函数在解构声明方面的用处。
解构声明
Kotlin 有时会把一个对象解构
成很多变量,使用起来会很方便:
fun testDeco() { val (msg, code) = Result("good", 1) println("msg:${msg} code:${code}") } inner class Result( private val msg: String, private val code: Int ) { operator fun component1() = msg operator fun component2() = code }
componentN()
函数是 Kotlin 中约定的获取解构声明变量的对应运算符,这里面需要使用 operator 进行描述以重载。
解构声明的变量如果不需要使用的话,可以用 _ 来代替,比如:
val (_, status) = getResult()
解构声明在 Kotlin 中使用非常普遍,比如遍历一个映射(map)最好的方式:
for ((key, value) in map) { ... }
operator 原理
反编译解构声明的示例代码,可以看到事实上定义了多个变量,每个变量按照顺序调用目标类中实现的 componentN() 函数进行赋值。
public final void testDeco() { Test.Result var3 = new Test.Result("good", 1); Object msg = var3.component1(); int code = var3.component2(); String var4 = "msg:" + msg + " code:" + code; System.out.println(var4); } public final class Result { private final String msg; private final int code; @NotNull public final Object component1() { return this.msg; } public final int component2() { return this.code; } ... }
结语
函数在 Kotlin 语法中极为重要,了解其特点和原理对于灵活编程非常必要,再次回顾下各函数的异同及原理。
参考
https://developer.android.com/kotlin/learn
Kotlin Functions
https://developer.android.com/kotlin/ktx?hl=zh-cn
Kotlin中,理解T.()->Unit 、 ()->Unit与(T) -> Unit
kotlin—匿名函数及其实现原理
Kotlin匿名函数
避免滥用 Kotlin 扩展函数
Kotlin 内联函数 inline
让人爱不释手的Kotlin扩展(Extensions)技术探Extensions秘与应用