kotlin 内联函数 Inline functions
Kotlin 内联函数 inline - 简书 (jianshu.com)
1.inline functions的作用
使用高阶函数会带来一定的运行时损失:每个函数都是一个对象,它捕获一个闭包。闭包是可以在函数体中访问的变量的范围。内存分配(对于函数对象和类)和虚拟调用引入了运行时开销。
但在许多情况下,这种开销似乎可以通过内联lambda表达式来消除。下面显示的函数是这种情况的很好的例子。
使用inline前
private fun ordinaryFunction(block: () -> Unit) {
println("ordinaryFunction")
block.invoke()
}
private fun foo() {
ordinaryFunction {
println("ordinaryFunction lambda invoke")
}
}
fun main() {
println("hello world")
println( measureTimeMillis {
repeat((0..100000).count()) {
foo()
}
})
}
生成的代码
public final class TestNotInlineKt {
private static final void ordinaryFunction(Function0 block) {
String var1 = "ordinaryFunction";
System.out.println(var1);
block.invoke();
}
private static final void foo() {
ordinaryFunction((Function0)null.INSTANCE);
}
public static final void main() {
String var0 = "hello world";
System.out.println(var0);
int $i$f$measureTimeMillis = false;
long start$iv = System.currentTimeMillis();
int var3 = false;
int var4 = 0;
var4 = CollectionsKt.count((Iterable)(new IntRange(var4, 100000)));
int var5 = 0;
while(var5 < var4) {
++var5;
int var8 = false;
foo();
}
long var10 = System.currentTimeMillis() - start$iv;
System.out.println(var10);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
把fun ordinaryFunction使用inline修饰后,
-private fun ordinaryFunction(block: () -> Unit) {...}
+private inline fun ordinaryFunction(block: () -> Unit) {}
继续看看生成的java代码变成如下
public final class TestNotInlineKt {
private static final void ordinaryFunction(Function0 block) {
int $i$f$ordinaryFunction = 0;
String var2 = "ordinaryFunction";
System.out.println(var2);
block.invoke();
}
private static final void foo() {
int $i$f$ordinaryFunction = false;
String var1 = "ordinaryFunction";
System.out.println(var1);
int var2 = false;
String var3 = "ordinaryFunction lambda invoke";
System.out.println(var3);
}
public static final void main() {
String var0 = "hello world";
System.out.println(var0);
int $i$f$measureTimeMillis = false;
long start$iv = System.currentTimeMillis();
int var3 = false;
int var4 = 0;
var4 = CollectionsKt.count((Iterable)(new IntRange(var4, 100000)));
int var5 = 0;
while(var5 < var4) {
++var5;
int var8 = false;
foo();
}
long var10 = System.currentTimeMillis() - start$iv;
System.out.println(var10);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
经对比,最大的变化是
public final class TestNotInlineKt {
private static final void ordinaryFunction(Function0 block) {
String var1 = "ordinaryFunction";
System.out.println(var1);
block.invoke();
}
private static final void foo() {
ordinaryFunction((Function0)null.INSTANCE);
}
}
变成了如下。
private static final void ordinaryFunction(Function0 block) {
int $i$f$ordinaryFunction = 0;
String var2 = "ordinaryFunction";
System.out.println(var2);
block.invoke();
}
private static final void foo() {
int $i$f$ordinaryFunction = false;
String var1 = "ordinaryFunction";
System.out.println(var1);
int var2 = false;
String var3 = "ordinaryFunction lambda invoke";
System.out.println(var3);
}
可以看出,调用foo()方法时,从方法调用方法并且传递对象变成了把原来ordinaryFunction里的方法内联/平铺到了foo()方法内。
可以看出以下几点
1:inline是编译期把代码优化
2.减少了内存嵌套调用,使得方法的调用栈少一层,
2.什么情况下需要使用inline
对一个普通方法(非高阶函数)使用inline
private inline fun noParamsFun() {
}
可以看到编译器看到警告
Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types
翻译过来就是
内联带来的预期性能影响是微不足道的。内联最适合参数为函数类型的函数
3.noinline
如果你不希望所有传递给内联函数的lambda都内联,用noinline修饰符标记一些函数参数:
内联有这么多好处,为什么有时又要标明非内联呢
假设有一个高阶函数,有两个参数都是函数类型。
private fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
println("ordinaryFunction")
block.invoke()
block2.invoke(System.currentTimeMillis())
}
private fun foo() {
ordinaryFunction(block = {
println("ordinaryFunction lambda block1 invoke")
}, block2 = { it ->
println("ordinaryFunction lambda block2 invoke $it")
})
}
fun main() {
println("hello world")
println(measureTimeMillis {
repeat((0..100000).count()) {
foo()
}
})
}
愉快的编译成功并运行,
加上inline后,也是愉快的编译并运行
-private fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
+private inline fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
println("ordinaryFunction")
block.invoke()
block2.invoke(System.currentTimeMillis())
}
貌似加不加inline都可以正常运行
但假设作为参数的函数,并不是只即执行,而是作为参数继续传递呢?
private fun testHigherOrderFunction( block2: (Long) -> Unit) {
block2.invoke(System.currentTimeMillis())
}
private inline fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
println("ordinaryFunction")
block.invoke()
//inline修饰的方法里的函数参数作为参数继续传递
testHigherOrderFunction(block2)
}
可以看到会编译失败,会提示
Add 'noinline' modifier to the parameter declaration
在参数声明中添加'noinline'修饰符
-private inline fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
+private inline fun ordinaryFunction(block: () -> Unit, noinline block2: (Long) -> Unit) {
println("ordinaryFunction")
block.invoke()
testHigherOrderFunction(block2)
}
可以看到生成的java代码出是没有内联block2函数的,只内联了block1函数。
private static final void foo() {
Function1 block2$iv = (Function1)null.INSTANCE;
int $i$f$ordinaryFunction = false;
String var2 = "ordinaryFunction";
System.out.println(var2);
int var3 = false;
String var4 = "ordinaryFunction lambda block1 invoke";
System.out.println(var4);
access$testHigherOrderFunction(block2$iv);
}
内联lambdas只能在内联函数内部调用或作为内联参数传递。然而,可以以您喜欢的任何方式操作非内联lambda,包括存储在字段中或传递。
inline fun 是内联所有的函数参数的,可以指定哪些需要内联,哪些不需要
4.inline与Non-local returns
在Kotlin中,只能使用普通的、非限定的返回来退出命名函数或匿名函数。要退出lambda,请使用标签。禁止在lambda表达式中使用裸返回,因为lambda表达式不能使外围函数返回:
private fun ordinaryFunction(block: () -> Unit) {
println("hi!")
block.invoke()
}
private fun foo() {
ordinaryFunction {
println("test return")
//ERROR: cannot make `foo` return here
// return //lambda不支持这样直接返回,需要使用标签
return@ordinaryFunction
}
//普通函数return
return
}
fun main() {
foo()
}
这样的返回(位于lambda中,但退出封闭函数)称为non-local returns.非本地返回。这类构造通常发生在循环中,内联函数通常包含:
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
}
fun main() {
println("test")
println(hasZeros(listOf(0,1,2,3,4,5,6,7,8,9)))
println(hasZeros(listOf(1,2,3,4,5,6,7,8,9)))
}
上面的代码之所以能在forEach里return到hasZeros的调用处,是因为forEach本身就是一个inline fun
/**
* Performs the given [action] on each element.
*/
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
看生成的java代码就一目了然了。
public final class TestNotUseInline2Kt {
public static final boolean hasZeros(@NotNull List ints) {
Intrinsics.checkNotNullParameter(ints, "ints");
Iterable $this$forEach$iv = (Iterable)ints;
int $i$f$forEach = false;
Iterator var3 = $this$forEach$iv.iterator();
int it;
do {
if (!var3.hasNext()) {
return false;
}
Object element$iv = var3.next();
it = ((Number)element$iv).intValue();
int var6 = false;
} while(it != 0);
return true;
}
public static final void main() {
String var0 = "test";
System.out.println(var0);
boolean var1 = hasZeros(CollectionsKt.listOf(new Integer[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
System.out.println(var1);
var1 = hasZeros(CollectionsKt.listOf(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9}));
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
kotlin里常见的inline函数 作用域函数。with/let/run/apply
/**
* Calls the specified function [block] and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
*/
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
/**
* Calls the specified function [block] with `this` value as its receiver and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
/**
* Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
/**
* Calls the specified function [block] with `this` value as its argument and returns `this` value.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
5.crossinline交叉内联
假设有以下函数
fun f(body: () -> Unit) {
val runnable = Runnable { body() }
runnable.run()
}
fun main() {
f {
println("running")
}
}
愉快的编译并运行
加上inline修改后
-fun f(body: () -> Unit) {}
+inline fun f(body: () -> Unit) {}
编译失败,查看提示
Can't inline 'body' here: it may contain non-local returns. Add 'crossinline' modifier to parameter declaration 'body'
不能在这里内联'body':它可能包含non-local returns非本地返回。为参数声明body添加'crossinline'修饰符
当然,解决方法是可以组body加上noinline声明即可编译通过,但这一个方法只有一个参数,noinline和inline一起使用,相当于inline多此一举。
-inline fun f(body: () -> Unit) {}
+inline fun f(noinline body: () -> Unit) {}
按提示加上crossinline修饰。
inline fun f(crossinline body: () -> Unit) {
val runnable = Runnable { body() }
runnable.run()
}
fun main() {
f {
println("running")
//return //直接return是不允许的。
return@f
}
}
看看生成的代码
public final class TestCrossinlineKt {
public static final void f(@NotNull final Function0 body) {
int $i$f$f = 0;
Intrinsics.checkNotNullParameter(body, "body");
Runnable runnable = (Runnable)(new Runnable() {
public final void run() {
body.invoke();
}
});
runnable.run();
}
public static final void main() {
int $i$f$f = false;
Runnable runnable$iv = (Runnable)(new TestCrossinlineKt$main$$inlined$f$1());
runnable$iv.run();
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
import kotlin.Metadata;
public final class TestCrossinlineKt$main$$inlined$f$1 implements Runnable {
public final void run() {
int var1 = false;
String var2 = "running";
System.out.println(var2);
}
}
对比不加inlin的代码
原kotlin代码
fun f(body: () -> Unit) {
val runnable = Runnable { body() }
runnable.run()
}
fun main() {
f {
println("running")
return@f
}
}
生成的代码
public final class TestCrossinlineKt {
public static final void f(@NotNull final Function0 body) {
Intrinsics.checkNotNullParameter(body, "body");
Runnable runnable = (Runnable)(new Runnable() {
public final void run() {
body.invoke();
}
});
runnable.run();
}
public static final void main() {
f((Function0)null.INSTANCE);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
6.具体化的类型参数 Reified type parameters
假设有一个这样的方法,用于类型转换
fun <T> Any.caseTo(): T? {
return this as? T
}
fun main() {
val r1 = "1".caseTo<String>()
val r2 = runCatching {
//这里会抛异常
"1".caseTo<Int>()
}
println("r1:$r1\tr2:$r2")
}
为了避免异常,一般会有以下方案
显式传递class信息
fun <T> Any.caseTo(clazz: Class<T>): T? {
return if(clazz.isInstance(this)) this as T else null
}
fun main() {
val r1 = "1".caseTo(String::class.java)
val r2 = runCatching {
//这里不再上抛异常,但返回null
1.caseTo(Int::class.java)
}
println("r1:$r1\tr2:$r2")
}
但有没有更加优雅的调用方式呢
例如
-"1".caseTo(String::class.java)
+"1".caseTo<String>()
为了实现这一个目的,kotlin提供的方法是:inline + reified(reified关键字必须和inline关键字一起使用)
把代码稍稍修改一下
inline fun <reified T> Any.caseTo(): T? {
return if(this is T) this as T else null
}
fun main() {
val r1 = "1".caseTo<String>()
val r2 = runCatching {
//这里不再上抛异常 并且正常返回值
1.caseTo<Int>()
}
println("r1:$r1\tr2:$r2")
}
reified与反射
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
7.Inline properties
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
inline var bar: Bar
get() = ...
set(v) { ... }
8.关于内联的其他一些事情
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // non-local return directly to the caller of foo()
print(it)
}
//因为上面的forEach是内联的,所以会直接结束foo()方法,而不是结束forEach,所以这下面这一句无法执行到的。
println("this point is unreachable")
}
fun main() {
foo()
}
声明返回到哪里
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach // local return to the caller of the lambda - the forEach loop
print(it)
}
print(" done with implicit label")
}