Kotlin内联(inline)功能何时使用?
什么是内联函数?
为了使函数内联,我们将在fun
之前添加inline
关键字,以将正常函数转换为内联函数
inline fun calculateTime() {
println("Calculate")
}
fun main() {
println("Start")
calculateTime()
println("End")
}
编译结果
public void main() {
System.out.println("Start");
System.out.println("Calculate");
System.out.println("End");
}
正如我们所看到的,内联函数的完整主体在编译时被插入函数调用站点。
正如我们所看到的,内联函数的完整主体在解编译时被插入函数调用站点。
内联函数的优势:无函数调用开销——更快的程序执行。
为什么不让每个功能都内联呢?
使每个函数内联最终会增长代码,因为相同的代码将在任何地方重复。
有些人会说,如果功能体很小,则使用内联,否则不会。这在某种程度上是正确的。
但是,当我们用正常参数内联正常函数时,编译器会给我们以下警告
Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types
因此,这意味着内联对性能的预期影响可以忽略不计,因为如果需要,编译器很可能会这样做。
我们什么时候内联功能?
当函数采用函数类型参数(lambdas
)时,我们通常更喜欢内联函数
让我们深入研究一下原因,以及在这种情况下内衬实际上如何帮助我们
在正常情况下:
fun calculateTime(block: ()->Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
}
println(time)
}
编译结果
public long calculateTime(Function0 block) {
long initialTime = System.currentTimeMillis();
block.invoke();
return System.currentTimeMillis() - initialTime;
}
public void main() {
long time = calculateTime(
new Function() {
@Override
public void invoke() {
System.out.print("Hello");
}
}
);
System.out.println(time);
}
public interface Function0<out R> : Function<R> {
public operator fun invoke(): R
}
在Kotlin中,函数类型(lambdas
)转换为扩展接口的匿名/常规类(Function0
)的对象Function
问题?—如果我们调用此函数(calculateTime
)100次,将创建100个Function类对象并收集垃圾。这会影响性能。
解决方案?—使用内联来防止对象创建
inline fun calculateTime(block: ()->Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
}
println(time)
}
编译结果
public void main() {
long initialTime = System.currentTimeMillis();
System.out.println("Hello");
long time = System.currentTimeMillis() - initialTime;
System.out.println(time);
}
因此,将其他函数作为参数的函数在内联时会更快(因为没有创建Function对象)。
当我们有具有小函数体的函数类型参数时,我们应该使用内联函数。
noinline
如果我们在内联函数中有多个函数类型参数,并且我们不想内联所有参数,我们可以使用noinline
关键字。
fun main() {
val time = calculateTime({
println("Hellow")
}, {
println("World")
})
println(time)
}
inline fun calculateTime(block1: () -> Unit, noinline block2: () -> Unit): Long {
val initialTime = System.currentTimeMillis()
block1.invoke()
block2.invoke()
return System.currentTimeMillis() - initialTime
}
编译结果
public void main() {
long initialTime = System.currentTimeMillis();
System.out.println("Hello");
Function block = new Function() {
@Override
public void invoke() {
System.out.print("World");
}
}
);
block.invoke();
long time = System.currentTimeMillis() - initialTime;
System.out.println(time);
}
crossinline
crossinline
关键字用于避免非本地返回。 让我们用例子来理解
inline fun calculateTime(block: () -> Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
return
}
println(time)
}
编译结果
public void main() {
long initialTime = System.currentTimeMillis();
System.out.println("Hello");
}
在这里,我们可以看到,返回后,没有写其他声明(计算和打印最终时间)。这是非本地回报。为了避免这个问题,我们可以将lambda
标记为crossinline
。
inline fun calculateTime(crossinline block: () -> Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
return // This will give compile time error
}
println(time)
}
当将lambda
参数标记为crossinline
时,如果我们添加return
语句,编译器将给出错误(‘return’ is not allowed here
)。
将内联用于泛型类型参数
如果我们想直接在Kotlin泛型中处理类类型呢?
fun <T> doSomething(value: T) {
println("Value: $value") // OK
println("Type: ${T::class.simpleName}") // Error
}
fun main() {
doSomething("something")
}
在上面的示例中,我们得到了错误 Cannot use ‘T’ as reified type parameter. Use a class instead
我们不能直接处理该类型,因为当我们传递给该函数时,类型参数在运行时被擦除。因此,我们不可能确切知道我们正在处理哪种类型。
解决方案?—使用内联函数和reified
类型参数。
inline fun <reified T> doSomething(value: T) {
println("Value: $value") // OK
println("Type: ${T::class.simpleName}") // OK
}
fun main() {
doSomething("something")
}
在上述示例中,实际类型参数将代替T。因此,T::class.simpleName
变为String::class.simpleName
。
reified
关键字只能与内联函数一起使用。