前言
Kotlin作为JVM系的语言,起源于Java又不同于Java。通过在语言层面比较两者的区别,可以使得开发者能够快速学习,融会贯通。
内联函数概念
内联函数主要是指在编译或加载时被扩展的函数。以一个简单的例子进行理解:
fun main(){
x = 1
y = 2
z = add(x, y)
}
add(x, y){
return x + y
}
如果将add方法作为内联函数处理,则add方法会在被调用的地方进行扩展,进而变成如下形式:
fun main(){
x = 1
y = 2
z = x + y
}
也就是说,这像是将内联函数add的代码复制到了调用的地方。
那为何要这么做呢?因为在程序语言中,调用方法需要将方法入栈,然后执行点会移动到栈中的方法处,执行完方法后,方法出栈,回到原来调用方法的地方继续执行。这一过程需要有时间和空间的开销。如果同一个方法被调用得非常频繁,那么会对性能造成一定影响。此时如果将该方法以内联函数的形式进行处理,即在编译或加载时最对进行展开,则避免了这部分的开销。
内联函数使用
-
Java
笔者查阅了一些资料,了解到在Java语言层面不支持内联函数,即没有任何关键字可以表示某个函数是内联函数。但是如果将方法声明为静态的或者final的,在编译成字节码文件后,JVM可能对字节码中的相应方法进行优化,即对其进行展开。但这一切仅仅是可能,是否优化以及怎样优化由JVM决定。 -
Kotlin
除了调用方法的开销,在Kotlin的高阶函数中,被传入的函数都能访问外部函数的变脸(即闭包)。这些使得内存分配与虚拟调用引入性能开销。为此,Kotlin提供了关键字inline对方法进行声明,表示内联函数,如:
inline fun <T> lock(lock: Lock, body: () -> T): T { …… }
inline 修饰符影响函数本身和传给它的 lambda 表达式:所有这些都将内联到调用处。
内联函数的适用场景:
- 方法被频繁调用
- 方法内语句少
内联函数优点:可能提升性能
内联函数缺点:生成的代码增加
内联的 lambda 表达式只能在内联函数内部调用或者作为可内联的参数传递, 但是对于不希望被内联的函数,可以使用关键字noinline进行声明。这些函数可以以任何我们喜欢的方式操作:存储在字段中、传送它等等。
内联函数返回
由于Java在语言层面不支持内联函数,故此部分内容以Kotlin为基础进行介绍。
若要在lambda表达式中返回一个值,有两种方式,分别是返回表达式中最后一个语句,或者通过return添加标签的形式,例如:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
在Kotlin中,lambda表达式内部禁止仅使用return进行返回,即不添加任何标签。因为 lambda 表达式不能使包含它的函数返回:
fun foo() {
ordinaryFunction {
return // 错误:不能使 `foo` 在此处返回
}
}
但是如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它是允许的:
fun foo() {
inlined {
return // OK:该 lambda 表达式是内联的
}
}
这种返回(位于 lambda 表达式中,但退出包含它的函数)称为非局部返回。
内联属性
inline 修饰符可用于没有幕后字段的属性的访问器。 可以标注独立的属性访问器:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ……
inline set(v) { …… }
同时,也可以标注整个属性,将它的两个访问器都标记为内联:
inline var bar: Bar
get() = ……
set(v) { …… }
在调用处,内联访问器如同内联函数一样内联。
网友评论