Kotlin
语言相信大家已经玩的很溜了,但大家有没有注意到它内部源码大量使用了inline
,那么Kotlin
为什么要使用inline
?它的作用又是什么呢?
如果你只是注意到了,但从来没有进行深入探究,相信这篇文章能够帮你找到答案。
inline
inline
是作用在函数方法上面的,例如Kotlin
的let
方法
1 2 3 4 5 6
| public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) }
|
那它的作用是什么呢?
inline
主要是对闭包block
做优化,为了对比它做的优化,我对应定义一个没有inline
的方法
1 2 3
| public fun <T, R> T.ret(block: (T) -> R): R { return block(this) }
|
然后我同时调用这两个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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, R> T.ret(block: (T) -> R): R { return block(this) } }
|
再通过AS
的Show Kotlin Bytecode
,来看它们反编译的二进制代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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
修饰的函数支持局部返回
还是上面的例子
1 2 3 4 5 6 7 8 9 10 11
| while (--i > 0) { let { return // success } } while (--i > 0) { ret { return // error: return is not allow here } }
|
简单的理解就是,使用inline
修饰的函数,可以直接在循环语句中通过return
跳出循环体。而非inline
函数是不支持的,它支持跳出方法体。
原因也很简单,回头再看之前的反编译的二进制代码,因为使用inline
修饰的方法是平铺式直接按顺序调用,并没有包含在方法体中,所以如果return
的话就相当于直接在循环体中return
1 2 3 4
| while(--i > 0) { return ... }
|
而未使用inline
修饰的方法,是在另外的方法体中进行调用,所以它的return
只能是返回到方法体。
reified
使用inline
修饰的函数还有一个好处是可以使用reified
来修饰函数的泛型,让函数的泛型具体化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| inline fun <reified T, R> T.det(block: (T) -> R): R { val a = 0 if (a is T) { // success } return block(this) } fun <T, R> 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 <reified T> membersOf() = T::class.members
|
传统的泛型是会在程序运行的过程中进行擦除操作,而使用reified
修饰的泛型,通过反编译二进制表现就是将泛型替换成具体的类型,不进行类型擦除。
1 2 3 4 5 6 7 8 9
| $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
的特性
1 2 3 4
| inline fun <T, R> T.net(block: (T) -> R, noinline noBlock: () -> Unit): R { noBlock() return block(this) }
|
这样就只有block
会执行inline
的优化。
crossinline
还有一种情况,如果使用了inline
修饰的函数,被使用到了嵌套的内联函数中,直接使用是会报错的,需要为函数参数添加crossinline
修饰符
1 2 3 4 5 6
| inline fun <T, R> T.cet(block: (T) -> R, crossinline noBlock: () -> Unit): R { Runnable { noBlock() } return block(this) }
|
今天有关kotlin
的inline
部分就分析到这,希望你有所收获。
项目
android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件。开发人员可以使用android-startup
来简化启动序列,并显式地设置初始化顺序与组件之间的依赖关系。 与此同时android-startup
支持同步与异步等待,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。
AwesomeGithub: 基于Github
客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin
语言进行开发,项目架构是基于Jetpack&DataBinding
的MVVM
;项目中使用了Arouter
、Retrofit
、Coroutine
、Glide
、Dagger
与Hilt
等流行开源技术。
flutter_github: 基于Flutter
的跨平台版本Github
客户端,与AwesomeGithub
相对应。
android-api-analysis: 结合详细的Demo
来全面解析Android
相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。
daily_algorithm: 算法进阶,由浅入深,欢迎加入一起共勉。