盒子
盒子
文章目录
  1. inline
    1. 非局部返回
    2. reified
  2. noinline
  3. crossinline
  4. 项目

一文彻底搞懂Kotlin inline

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: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App 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: 每日一算法,由浅入深,欢迎加入一起共勉。

支持一下
赞赏是一门艺术