Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2019-10-21:在Kotlin中,什么是内联函数?有什么作用? #169

Open
Moosphan opened this issue Oct 21, 2019 · 8 comments
Open
Labels

Comments

@Moosphan
Copy link
Owner

No description provided.

@zhaoerlei1989
Copy link

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() 方法体内的字节码了。

  1. 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 startActivity() {
startActivity(Intent(this, T::class.java))
}
使用时直接传入泛型即可,代码简洁明了:

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

@xzkuse
Copy link

xzkuse commented Oct 21, 2019

对比java
函数反复调用时,会有压栈出栈的性能消耗

kotlin优化 内联函数 用来解决 频繁调用某个函数导致的性能消耗

使用 inline标记
内联函数,调用非内联函数会报错,,需要加上noinline标记

noinline,让原本的内联函数形参函数不是内联的,保留原有数据特征

crossinline
非局部返回标记
为了不让lamba表达式直接返回内联函数,所做的标记
相关知识点:我们都知道,kotlin中,如果一个函数中,存在一个lambda表达式,在该lambda中不支持直接通过return退出该函数的,只能通过return@XXXinterface这种方式

reified 具体化泛型
java中,不能直接使用泛型的类型
kotlin可以直接使用泛型的类型

使用内联标记的函数,这个函数的泛型,可以具体化展示,所有 能解决方法重复的问题

@Petterpx
Copy link

上面的老哥都讲的很好,但是大多数同学都用的是java,难免有些不友好,所以我用koltin+java字节码的方式,便于大家有一个概念(虽然暂时没有使用kotlin)。
后面3个看不太懂,还得继续学习。
下面,我用实际操作来演示吧;

public class Test {
    public static void main(String[] args) {
        System.out.println(sum(10)+sum(5));
    }

    private static int sum(int a){
        int sum=8;
        sum+=a;
        return sum;
    }
}

这是一段java代码,简单的不能再简单了吧,就是重复的相加,别注意逻辑,只是为了演示。

同样的kotlin代码:

inline fun sum(a: Int): Int {
    var sum = 8
    sum += a
    return sum
}

fun main() {
    println(sum(10)+ sum(5))
}

虽然一眼看上去很简洁,但我们的关注点不在这里,在 inline 关键字上面。为了便于大家学习,我通过查看字节码的方式来转成相应的 java 代码,便于大家更好的理解。

没加 inline 之前

image-20191021220114042

加上 inline 之后

image-20191021220149030

解释就不用多说了吧,kotlin 自动帮我们将方法在编译期就加在了相应的调用处,免除了 java 中的入方法栈与退栈。

后面的有点看不太懂,所以还得再看看。

@shuandroid
Copy link

shuandroid commented Oct 30, 2019

感觉上面部分还有一些不太合适,只说了它的使用和意义,并没有讨论 inline 什么时候应该使用什么时候不该使用,以及为什么在 kotlin 中需要 inline

inline 真正发挥它的作用的是在包含 lambda 参数的函数中使用 inline 注解,这时才会真正的起到它的节省开销的作用,因为 kotlin 中有大量的高阶函数。

前段时间自己总结了一下,有兴趣可以看一下这个链接:https://www.jianshu.com/p/8a0d5bae9cdf

@Xiaocai9527
Copy link

上面的老哥都讲的很好,但是大多数同学都用的是java,难免有些不友好,所以我用koltin+java字节码的方式,便于大家有一个概念(虽然暂时没有使用kotlin)。
后面3个看不太懂,还得继续学习。
下面,我用实际操作来演示吧;

public class Test {
    public static void main(String[] args) {
        System.out.println(sum(10)+sum(5));
    }

    private static int sum(int a){
        int sum=8;
        sum+=a;
        return sum;
    }
}

这是一段java代码,简单的不能再简单了吧,就是重复的相加,别注意逻辑,只是为了演示。

同样的kotlin代码:

inline fun sum(a: Int): Int {
    var sum = 8
    sum += a
    return sum
}

fun main() {
    println(sum(10)+ sum(5))
}

虽然一眼看上去很简洁,但我们的关注点不在这里,在 inline 关键字上面。为了便于大家学习,我通过查看字节码的方式来转成相应的 java 代码,便于大家更好的理解。

没加 inline 之前

image-20191021220114042

加上 inline 之后

image-20191021220149030

解释就不用多说了吧,kotlin 自动帮我们将方法在编译期就加在了相应的调用处,免除了 java 中的入方法栈与退栈。

后面的有点看不太懂,所以还得再看看。

浅显易懂,小弟看懂了

@Krosxx
Copy link

Krosxx commented Jun 8, 2020

inline 还可以解决获取泛型的class问题:

inline fun < refied T> String.json2Obj() : T{
   return Gson().fromJson<T>(this, T::class.java)
}

(代码可能有问题

@huanzhiyazi
Copy link

inline 还可以解决获取泛型的class问题:

inline fun <T> String.json2Obj() : T{
   return Gson().fromJson<T>(this, T::class.java)
}

(代码可能有问题

需要加上实化关键字 refied:

inline fun <refied T> String.json2Obj() : T{
   return Gson().fromJson<T>(this, T::class.java)
}

@senlinxuefeng
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants