一文彻底搞懂kotlin inline
小憩第55篇原创文章
Kotlin
语言相信大家已经玩的很溜了,但大家有没有注意到它内部源码大量使用了inline
,那么Kotlin
为什么要使用inline
?它的作用又是什么呢?
如果你只是注意到了,但从来没有进行深入探究,相信这篇文章能够帮你找到答案。
inline
inline
是作用在函数方法上面的,例如 Kotlin
的 let
方法
public inline fun T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) }
那它的作用是什么呢?
inline
主要是对闭包 block
做优化,为了对比它做的优化,我对应定义一个没有 inline
的方法
public fun T.ret(block: (T) -> R): R { return block(this) }
然后我同时调用这两个方法
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) let { it.a() } ret { it.b() } } fun a() { } fun b() { } fun T.ret(block: (T) -> R): R { return block(this) } }
再通过 AS
的 Show Kotlin Bytecode
,来看它们反编译的二进制代码
public final class MainActivity extends AppCompatActivity { private HashMap _$_findViewCache; protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(1300009); boolean var3 = false; boolean var4 = false; // inline修饰的let MainActivity it = (MainActivity)this; int var6 = false; it.a(); // 没有inline修饰的ret this.ret(this, (Function1)null.INSTANCE); } public final void a() { } public final void b() { } public final Object ret(Object $this$ret, @NotNull Function1 block) { Intrinsics.checkParameterIsNotNull(block, "block"); return block.invoke($this$ret); } ... }
不懂的还是要看源码,程序员的终结武器
在这里我们发现通过 inline
修饰的方法,会通过平坦式的方式直接在后面按执行顺依次调用。
而没有使用 inline
修饰的方法,则会为 block
方法创建一个 Function1
实例。
简单的理解就是未使用 inline
修饰的方式,会对带有函数式参数的方法,创建对于函数的实例,再将这个实例传递到方法参数中。该参数方法最终在原方法的内部被显示调用。
所以 inline
做的优化就是将带有函数参数的方法简化成没函数式参数的直接调用。好处是提高程序的性能。
当然需要注意的是,避免使用 inline
内联大型函数,减少方法中代码的增长。
非局部返回
inline
还有一个好处是,对于 while
、 for
等语句,被 inline
修饰的函数支持局部返回
还是上面的例子
while (--i > 0) { let { return // success } } while (--i > 0) { ret { return // error: return is not allow here } }
简单的理解就是,使用 inline
修饰的函数,可以直接在循环语句中通过 return
跳出循环体。而非 inline
函数是不支持的,它支持跳出方法体。
原因也很简单,回头再看之前的反编译的二进制代码,因为使用 inline
修饰的方法是平铺式直接按顺序调用,并没有包含在方法体中,所以如果 return
的话就相当于直接在循环体中 return
while(--i > 0) { return ... }
而未使用 inline
修饰的方法,是在另外的方法体中进行调用,所以它的 return
只能是返回到方法体。
reified
使用 inline
修饰的函数还有一个好处是可以使用 reified
来修饰函数的泛型,让函数的泛型具体化
inline fun T.det(block: (T) -> R): R { val a = 0 if (a is T) { // success } return block(this) } fun T.ret(block: (T) -> R): R { val a = 0 if (a is T) { // error: Cannot check for instance of erased type: T } return block(this) } // 或者 inline fun membersOf() = T::class.members
传统的泛型是会在程序运行的过程中进行擦除操作,而使用 reified
修饰的泛型,通过反编译二进制表现就是将泛型替换成具体的类型,不进行类型擦除。
$i$f$det = false; int a$iv = 0; if (Integer.valueOf(a$iv) instanceof MainActivity) { this.a(); } MainActivity it = (MainActivity)this; int var7 = false; it.b();
noinline
有了 inline
自然也有对立的 noinline
。
对于多个函数方法参数,可以使用 noinline
来指定某个函数方法参数不使用 inline
的特性
inline fun T.net(block: (T) -> R, noinline noBlock: () -> Unit): R { noBlock() return block(this) }
这样就只有 block
会执行 inline
的优化。
crossinline
还有一种情况,如果使用了 inline
修饰的函数,被使用到了嵌套的内联函数中,直接使用是会报错的,需要为函数参数添加 crossinline
修饰符
inline fun T.cet(block: (T) -> R, crossinline noBlock: () -> Unit): R { Runnable { noBlock() } return block(this) }
今天有关 kotlin
的 inline
部分就分析到这,希望你有所收获。
推荐阅读