Kotlin内联函数

Kotlin里使用关键 inline 来表示内联函数,那么到底什么是内联函数呢,内联函数有什么好处呢?

1. 什么是内联inline?

在 Java 里是没有内联这个概念的,所有的函数调用都是普通方法调用,如果了解 Java 虚拟机原理的,可以知道 Java 方法执行的内存模型是基于 Java 虚拟机栈的:每个方法被执行的时候都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧入栈、出栈的过程。

也就是说每调用一个方法,都会对应一个栈帧的入栈出栈过程,如果你有一个工具类方法,在某个循环里调用很多次,那就会对应很多次的栈帧入栈、出栈过程。这里首先要记住的一点是,栈帧的创建及入栈、出栈都是有性能损耗的。下面以一个例子来说明,看段代码片段:


fun test() {
    //多次调用 sum() 方法进行求和运算
    println(sum(1, 2, 3))
    println(sum(100, 200, 300))
    println(sum(12, 34))
    //....可能还有若干次
}

/**
 * 求和计算
  */
fun sum(vararg ints: Int): Int {
    var sum = 0
    for (i in ints) {
        sum += i
    }
    return sum
}

在测试方法 test() 里,我们多次调用了 sum() 方法。为了避免多次调用 sum() 方法带来的性能损耗,我们期望的代码类似这样子的:

fun test() {
    var sum = 0
    for (i in arrayOf(1, 2, 3)) {
        sum += i            
    }
    println(sum)
    
    sum = 0
    for (i in arrayOf(100, 200, 300)) {
        sum += i            
    }
    println(sum)
    
    sum = 0
    for (i in arrayOf(12, 34)) {
        sum += i            
    }
    println(sum)
}

3次数据求和操作,都是在 test() 方法里执行的,没有之前的 sum() 方法调用,最后的结果依然是一样的,但是由于减少了方法调用,虽然代码量增加了,但是性能确提升了。那么怎么实现这种情况呢,一般工具类有很多公共方法,我总不能在需要调用这些公共方法的地方,把代码复制一遍吧,内联就是为了解决这一问题。

定义内联函数:

inline fun sum(vararg ints: Int): Int {
    var sum = 0
    for (i in ints) {
        sum += i
    }
    return sum
}

如上所示,用关键字 inline 标记函数,该函数就是一个内联函数。还是原来的 test() 方法,编译器在编译的时候,会自动把内联函数 sum() 方法体内的代码,替换到调用该方法的地方。查看编译后的字节码,会发现 test() 方法里已经没了对 sum() 方法的调用,凡是原来代码里出现 sum() 方法调用的地方,出现的都是 sum() 方法体内的字节码了。

2. noinline

如果一个内联函数的参数里包含 lambda表达式,也就是函数参数,那么该形参也是 inline 的,举个例子:

inline fun test(inlined: () -> Unit) {...}

这里有个问题需要注意,如果在内联函数的内部,函数参数被其他非内联函数调用,就会报错,如下所示:

//内联函数
inline fun test(inlined: () -> Unit) {
    //这里会报错
    otherNoinlineMethod(inlined)   
}

//非内联函数
fun otherNoinlineMethod(oninline: () -> Unit) {
    
}

要解决这个问题,必须为内联函数的参数加上 noinline 修饰,表示禁止内联,保留原有函数的特性,所以 test() 方法正确的写法应该是:

inline fun test(noinline inlined: () -> Unit) {
    otherNoinlineMethod(inlined)
}

3. crossinline

首先来理解一个概念:非局部返回。我们来举个栗子:

fun test() {
    innerFun {      
        //return  这样写会报错,非局部返回,直接退出 test() 函数。
        return@innerFun     //局部返回,退出 innerFun() 函数,这里必须明确退出哪个函数,写成 return@test 则会退出 test() 函数
    }
    
    //以下代码依旧会执行
    println("test...")
}

fun innerFun(a: () -> Unit) {
    a()
}

非局部返回我的理解就是返回到顶层函数,如上面代码中所示,默认情况下是不能直接 return 的,但是内联函数确是可以的。所以改成下面这个样子:

fun test() {
    innerFun {      
        return //非局部返回,直接退出 test() 函数。
    }
    
    //以下代码不会执行
    println("test...")
}

inline fun innerFun(a: () -> Unit) {
    a()
}

也就是说内联函数的函数参数在调用时,可以非局部返回,如上所示。那么 crossinline 修饰的 lambda 参数,可以禁止内联函数调用时非局部返回。

fun test() {
    innerFun {      
        return //这里这样会报错,只能 return@innerFun
    }
    
    //以下代码不会执行
    println("test...")
}

inline fun innerFun(crossinline a: () -> Unit) {
    a()
}

4. 具体化的类型参数 reified

这个特性我觉得特别牛逼,有了它可以少些好多代码。在 Java 中是不能直接使用泛型的类型的,但是在 Kotlin 中确可以。举个栗子:

inline fun <reified T: Activity> startActivity() {
    startActivity(Intent(this, T::class.java))
}

使用时直接传入泛型即可,代码简洁明了:

startActivity<MainActivity>()

5. 小结

网上很多学习教程对内联函数的讲解都是千篇一律,说实话刚开始很难理解。本文尝试着用最简单的例子,来讲清楚什么是内联函数。在Java中我们一般会有很多工具类、工具方法,在Kotlin中则没有了工具类一说,通常都是将工具方法设计成顶层的内联函数来使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266