Kotlin 中的 inline 和 reified 关键字
在 Kotlin 里,inline 和 reified 经常成对出现,但它们各自解决的问题并不一样。下面把两个关键字拆开讲,再给出它们“合体”时的典型用法。
- inline —— 消除“函数对象”的开销
背景
Kotlin 的高阶函数(参数里带 lambda)会被编译成对象。每次调用都生成一个匿名类/对象,带来:
- 内存分配
- 虚方法调用
- 运行时实例化
inline 做了什么
把函数体以及传给它的 lambda 字节码原地展开(copy-paste),彻底消除上述开销。
示例
// 未 inline
fun measure(block: () -> Unit) {val t0 = System.nanoTime()block()println("cost ${System.nanoTime() - t0} ns")
}// 调用
measure { println("hello") }
反编译后能看到 measure 里出现了 Function0 实例。
加上 inline:
inline fun measure(block: () -> Unit) {val t0 = System.nanoTime()block()println("cost ${System.nanoTime() - t0} ns")
}
反编译结果里只剩下一行 println("hello") 和计时代码,没有任何 Function0。
额外效果
return可以“非局部”返回(lambda 里直接返回外层函数)。- 整个调用栈在调试器里变“平”,断点会停在展开后的行号。
- reified —— 把“擦除”的类型“找回来”
背景
JVM 泛型在运行时会被擦除,因此下面代码编译失败:
fun <T> Bundle.get(): T? = this.getSerializable("key") as? T // 警告:unchecked cast
问题
运行时无法判断 T 具体是什么,因而无法安全转换。
reified 做了什么
只有 inline 函数才能加 reified;它把类型实参也写进字节码里,于是运行时就能拿到 Class 对象。
示例
inline fun <reified T> Bundle.get(): T? =if (T::class.java.isPrimitive) nullelse this.getSerializable("key") as? T
调用
val user: User? = bundle.get<User>()
反编译后能看到:
User $tmp = (User) bundle.getSerializable("key");
User.class 被直接硬编码在调用处,因此运行期可以安全强转。
- 合体:inline + reified 的典型套路
- 启动 Activity(Android)
inline fun <reified T : Activity> Context.startActivity() {startActivity(Intent(this, T::class.java))
}
调用:
startActivity<DetailActivity>()
- 获取日志 Tag
inline fun <reified T> T.logger(): Logger = LoggerFactory.getLogger(T::class.java)
- Gson 一行反序列化
inline fun <reified T> Gson.fromJson(json: String): T = fromJson(json, T::class.java)
- 小结一句话
inline是性能+控制流关键字:消除 lambda 对象、允许非局部返回。reified是类型信息关键字:只能配合 inline,让泛型类型在运行期“不擦除”。
