1 内联函数
1.1 什么是内联函数
看以下forEach()
高阶函数的调用:
//Kotlin
//CodeSegment1
var ints = intArrayOf(1, 2, 3, 4)
fun main() {
ints.forEach {
println(it)
}
}
在main()
函数内调用了ints
的forEach()
高阶函数,正常一个函数的调用过程是一个压栈、调用执行、出栈的过程,如果被调用函数执行内容过于简单,例如这里的println(it)
,相对于调用执行的开销,压栈和出栈的开销将会过大。为了解决这个问题,内联函数的概念出现了。
内联函数在Kotlin中用inline
关键字标识,在内联函数被调用的时候,编译器直接将直接把函数的执行内容“插入”到调用函数的位置,以避免压栈和出栈的开销。
我们看一下IntArray
的forEach()
的定义:
//Kotlin
public inline fun kotlin.IntArray.forEach(action: (kotlin.Int) -> kotlin.Unit): kotlin.Unit { /* compiled code */ }
因为有inline
关键字,所以编译器在执行CodeSegment1时,完全等价于以下代码:
//Kotlin
//CodeSegment2
var ints = intArrayOf(1,2,3,4)
fun main() {
for (i in ints) {
println(i)
}
}
避免了调用函数带来的压栈、出栈开销。
定义函数时,在前面添加一个inline
关键字,该函数就被定义成内联函数。
注意!并非所有函数都适合定义成内联函数,需要看函数执行的开销,往往函数执行开销越小,调用函数的压栈和出栈开销越“不划算”,越应该定义成内联函数。
1.2 内联函数与高阶函数更配
高阶函数要么传入参数包含函数类型,要么输出结果是函数类型,所以高阶函数直接调用产生的压栈、出栈开销将比较大,所以一般都会定义成内联函数。
定义一个计算代码块执行耗时的高阶函数cost(block: ()->Unit)
:
//Kotlin
inline fun cost(block: () -> Unit) {
val start: Long = System.currentTimeMillis()
block()
println("${System.currentTimeMillis() - start} ms")
}
当我们在main()
函数中调用cost()
函数时:
//Kotlin
fun main() {
cost { println("Hello inline function") }
}
编译器实际执行的代码相当于:
//Kotlin
fun main() {
val start: Long = System.currentTimeMillis()
println("Hello inline function")
println("${System.currentTimeMillis() - start} ms")
}
这里发生了两次内联:
- 函数本身被内联到调用处;
- 函数的函数参数被内联到调用处。
1.3 内联函数的return
1.3.1 local return
设想这样一个场景,现在需要打印CodeSegment1中的ints
,但是,当遇到3
时,不打印,如何在forEach()
中实现这一点呢?操作如下:
//Kotlin
var ints = intArrayOf(1, 2, 3, 4)
fun main() {
ints.forEach {
if (it == 3) return@forEach
println(it)
}
println("Dividing")
ints.forEach {
if (it == 3) return
println(it)
}
println("Ending")
}
控制台打印:
return@forEach
和return
的打印结果完全不同。
return@forEach
将提前结束本次循环,相当于continue
;return
将结束所在函数,此处即为main()
函数,因为Ending
没有打印。
像return@forEach
这样的返回称为local return。
1.3.2 non-local return
上面的return
就是non-local return。
看这样一个例子,如果将高阶函数定义成内联函数:
//Kotlin
inline fun nonLocalReturn(block: ()->Unit){
block()
}
fun main() {
println("Starting")
nonLocalReturn { return }
println("Ending")
}
控制台将打印:
如果将高阶函数定义成非内联函数:
则只能执行local return。
对于内联函数,因为没有压栈和出栈操作,所以直接return
对内联函数无效,效果将出现在包裹内联函数的函数,这里是main()
函数。
看这样一个例子:
//Kotlin
inline fun func1(block:()->Unit){
println("func1 starting")
block()
println("func1 ending")
}
inline fun func2(){
println("func2 starting")
return
println("func2 ending")
}
fun main() {
func1 {
func2()
}
}
Q:func1 ending
会不会打印?
A:会打印:
按理说,func2()
也没有压栈操作,return
操作对其应该无效,同样的,包裹func2()
的func1()
也没有压栈操作,return
对func1()
也无效,这个return
应该直接使得main()
函数弹出栈,不打印func1 ending
才对?
字节码反编译如下:
//Java
package imooc.chapter_6.inline;
import kotlin.Metadata;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 2,
d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\u001a\u0017\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00010\u0003H\u0086\b\u001a\t\u0010\u0004\u001a\u00020\u0001H\u0086\b\u001a\u0006\u0010\u0005\u001a\u00020\u0001¨\u0006\u0006"},
d2 = {"func1", "", "block", "Lkotlin/Function0;", "func2", "main", "LearnKotlin.main"}
)
public final class Inline_funcKt {
public static final void func1(@NotNull Function0 block) {
int $i$f$func1 = 0;
Intrinsics.checkParameterIsNotNull(block, "block");
String var2 = "func1 starting";
boolean var3 = false;
System.out.println(var2);
block.invoke();
var2 = "func1 ending";
var3 = false;
System.out.println(var2);
}
public static final void func2() {
int $i$f$func2 = 0;
String var1 = "func2 starting";
boolean var2 = false;
System.out.println(var1);
}
public static final void main() {
int $i$f$func1 = false;
String var1 = "func1 starting";
boolean var2 = false;
System.out.println(var1);
int var3 = false;
int $i$f$func2 = false;
String var5 = "func2 starting";
boolean var6 = false;
System.out.println(var5);
var1 = "func1 ending";
var2 = false;
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
怎么解释?
1.3.3 crossinline
添加crossinline
关键字,禁止non-local return
:
1.3.4 noinline
添加noinline
关键字禁止函数参数内联:
这样的情况下,高阶函数前面的inline
关键字将是多余的:
2 内联属性
没有back-field的属性的getter/setter可以设置内联;
3 内联函数的限制
public/protected
的内联函数只能访问对应类的public
成员;- 内联函数的内联函数参数不能被存储(不能赋值给变量);
- 内联函数的内联函数参数只能被传递给其他的内联函数。