Kotlin之inline(内联)
inline内联
内联类
inline为内联的标识符
- 内联类(inline class)
- 内联方法(inline fun)
- 内联属性
内联类的唯一作用是成为某种类型的包装
有如下限制
- 最多一个参数(类型不受限制)
- 没有backing fields
- 不能有init块
- 不能继承其他类
- 可以实现接口
- 可以具有属性和方法
内联类中有一个装箱和拆箱的概念和操作
内联类有节约系统性能开销的作用 但不是一定会节约(鉴于使用者的使用方式,节约性能也是针对一般的包装器而言)
参数被用作其他类型时会被装箱
(参数类型为:Any\可空类型\泛型\基类或用于equals方法 时 内联类会被装箱 意味着创建一个新的对象 并不会节约性能)
inline class A(
val num: Long
)
fun test() {
val a1 = A(1L)
val a2 = A(2L)
a1 == a2 //a1 和 a2 都没有被装箱
a1.equals(a2) //a1 是原生类型但是 a2 被装箱了(因为equals接收的参数为Any?类型)
}
可空对象(因为只有对象可以为空 所以会被装箱 除了String类型)
集合
inline class A(
val num: Long
)
fun test() {
/**
* 装箱
* CollectionsKt.listOf的方法签名是:fun <T> listOf(element: T): List<T>
* T为泛型 所以这里会被装箱
*/
val list = listOf(A(1L))
}
内联类可以缩小扩展范围
inline class A(val value: String)
/**
* 如此扩展则不直接扩展String类型 而是扩展其包装器
* 就不会污染String的方发表
* 就不会出现调用String方法时 出现大量自定义扩展的方法表
*/
fun A.xxx() {
TODO()
}
内联类与类型别名
两者都在运行时表示为基础类型
区别在于:
类型别名与其基础类型(以及具有相同基础类型的其他类型别名)是赋值兼容的,而内联类却不是这样。
总之,内联类引入了一个真实的新类型,与类型别名正好相反,类型别名仅仅是为现有的的类型取了个新的替代名称(别名)
typealias TypeAlias = String
inline class InlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptTypeAlias(n: TypeAlias) {}
fun acceptInlineClass(p: InlineClass) {}
fun main() {
val alias: TypeAlias = ""
val inlineClass: InlineClass = InlineClass("")
val string: String = ""
acceptString(alias) // 正确: 传递别名类型的实参替代函数中基础类型的形参
acceptString(inlineClass) // 报错: 不能传递内联类的实参替代函数中基础类型的形参
acceptTypeAlias(string) // 正确: 传递基础类型的实参替代函数中别名类型的形参
acceptInlineClass(string) // 报错: 不能传递基础类型的实参替代函数中内联类类型的形参
}
内联函数
- inline
- noinline
- crossinline
内联函数是以增加编译时代码量的代价来换取运行时的时间开支 从而达到优化代码的目的
在编译期将内联函数调用的具体地方换成函数体
一般函数(非内联函数)被调用的时候都会创建一个栈帧(并且有相应的进出栈的操作)
(Java中调用函数会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息
每一个方法被调用直至执行完成的过程,就对应着一个栈帧入栈、出栈的过程。这一个过程是会有性能消耗的)
kotlin为了优化这种消耗 出现了inline关键字
不会出现函数调用 而是编译期的代码替换
而内联函数则不会如此
从而内联函数比一般函数少了一个栈帧的创建以及进出栈的操作
达到优化的目的
不过
这种优化几乎可以忽略不计
那么
问题来了
既然可以忽略不计
那为何要使用inline呢?
内联函数的正确使用方式是配合高阶函数使用
避免函数参数的时间开支
特别是高阶函数出现在循环里面的时候
inline fun a(head: () -> Unit, foot: () -> Unit) {
head()
println("body")
foot()
}
fun main() {
for (i in 1..1000) {
a({
println("head")
}, {
println("foot")
})
}
}
以上代码会被优化为
inline fun a(head: () -> Unit, foot: () -> Unit) {
head()
println("body")
foot()
}
fun main() {
for (i in 1..1000) {
println("head")
println("body")
println("foot")
}
}
这样的话就不会出现循环调用函数
而导致的循环创建新的栈帧
造成性能大量损耗的问题
因为内联函数在编译时
会被替换为函数体内部的代码
从而编译后这个函数就不存在了
也就没有函数调用这一说
那也就不会出现性能损耗的问题了
内联函数使用标准
那么
怎么判断内联函数在什么情况下使用
什么情况下不去使用呢?
在使用高阶函数(特别是函数的参数类型为函数类型)的时候加上inline就ok了
因为我们定义一个高阶函数后 没有办法去全面考虑其调用处是否会出现在循环当中
(这就会是一个性能安全隐患)
所以加上inline来避免这种隐患
当然 这只是一个粗略的判断标准(不过一般如此判断就行了)
在某些情况下需要酌情使用 譬如 代码量有限制…
内联函数中的noinline
在内联函数中
某个函数参数不需要被内联时
使用noinline
那么 这又有什么意义呢?
inline fun a(noinline head: () -> Unit, foot: () -> Unit): () -> Unit {
head()
println("body")
foot()
return head
}
如果
一个内联函数的返回值类型是函数类型
如上代码中
需要返回head函数对象
就需要加上noinline
如果不加
是不能通过编译的
(因为内联函数a的函数类型参数 head()和foot()也默认为内联函数 那么在编译后 函数参数head()的这个函数对象head是不存在的 那么返回值为函数对象head又怎么会通过编译呢 对象head都不存在了呢[head对象都没有了 如何返回head对象呢] 所以 需要加上noinline才能返回函数对象head)这里的head有点重复了 为了结合如上的代码具体说明 才加上如此多的head
内联函数中的crossinline
inline fun a(head: () -> Unit, foot: () -> Unit) {
head()
println("body")
foot()
}
fun main() {
a({
println("head")
}, {
println("foot")
return //重点在此 有一个return 那么 这个return是返回哪个函数呢?a()函数吗?再想想?
})
}
上面的代码会被优化为
inline fun a(head: () -> Unit, foot: () -> Unit) {
head()
println("body")
foot()
}
fun main() {
println("head")
println("body")
println("foot")
return //这样看 就能一目了然 return 实际是返回main()函数 而不是a()函数
}
crossinline关键字是限制内联函数不能有直接返回关键字return
(当然 可以有在lambda表达式中使用的[email protected]的局部返回 这里不做讨论)
inline fun a(crossinline head: () -> Unit, foot: () -> Unit) {
head()
println("body")
foot()
}
fun main() {
a({
println("head")
return //这里会报错 'return' is not allowed here 因为上面对foot()这个函数参数做了crossinline限制
}, {
println("foot")
})
}
那么 这又有什么用呢?
直观一点的解释为
如上的foot()函数在实际情况中必须要执行的时候(包含关键逻辑或运算)
我们就必须禁止在head()函数中加上return
否则foot()函数就有可能没办法执行(关键逻辑或运算有可能不能执行)
这样的情况下
就需要对head()函数加上crossinline关键字