Kotlin 中的 reified 关键字
reified(具体化)是 Kotlin 中的一个特殊关键字,用于在泛型函数中保留类型参数的类型信息,使得在运行时可以访问这些类型信息。
1. 为什么需要 reified?
类型擦除问题
在 JVM 中,泛型存在类型擦除(Type Erasure),这意味着在运行时无法知道泛型类型参数的具体类型:
// 普通泛型函数 - 运行时不知道 T 的具体类型
fun <T> checkType(obj: Any): Boolean {
// 编译错误:Cannot check for instance of erased type: T
// return obj is T
return false
}
2. 基本用法
语法规则
- 只能与
inline(内联)函数一起使用 - 放在类型参数前
inline fun <reified T> functionName() {
// 现在可以在运行时访问 T 的类型信息
}
3. 实际示例
示例 1:类型检查
inline fun <reified T> isTypeOf(value: Any): Boolean {
return value is T // 现在可以检查类型了!
}
// 使用
val result1 = isTypeOf<String>("hello") // true
val result2 = isTypeOf<Int>("hello") // false
// 自动推断类型
val str: Any = "Kotlin"
if (isTypeOf<String>(str)) {
println("这是一个字符串: ${str.length}") // 智能转换生效
}
示例 2:获取 KClass
inline fun <reified T> getClassName(): String {
return T::class.simpleName ?: "Unknown"
}
// 使用
println(getClassName<String>()) // 输出: String
println(getClassName<List<Int>>()) // 输出: List
4. 常见应用场景
场景 1:Android Intent 启动
inline fun <reified T : Activity> Context.startActivity(
vararg params: Pair<String, Any?>
) {
val intent = Intent(this, T::class.java) // 获取具体 Activity 类
params.forEach { (key, value) ->
when (value) {
is Int -> intent.putExtra(key, value)
is String -> intent.putExtra(key, value)
is Parcelable -> intent.putExtra(key, value)
// 其他类型...
}
}
startActivity(intent)
}
// 使用 - 非常简洁!
startActivity<MainActivity>(
"user_id" to 123,
"user_name" to "John"
)
场景 2:JSON 反序列化
inline fun <reified T> Gson.fromJson(json: String): T {
return this.fromJson(json, object : TypeToken<T>() {}.type)
}
// 使用
data class User(val id: Int, val name: String)
val gson = Gson()
val user: User = gson.fromJson("""{"id": 1, "name": "Alice"}""")
// 无需传递 User::class.java 或 TypeToken
场景 3:依赖注入
inline fun <reified T> inject(): Lazy<T> {
return lazy {
// 通过类型 T 获取实例
dependencyContainer.getInstance(T::class)
}
}
// 使用
val viewModel: MainViewModel by inject()
val repository: UserRepository by inject()
场景 4:RecyclerView ViewHolder 工厂
inline fun <reified VH : RecyclerView.ViewHolder> createViewHolder(
parent: ViewGroup,
layoutId: Int
): VH {
val view = LayoutInflater.from(parent.context)
.inflate(layoutId, parent, false)
// 通过反射创建 ViewHolder 实例
val constructor = VH::class.java.getConstructor(View::class.java)
return constructor.newInstance(view)
}
5. 高级用法
多个具体化类型参数
inline fun <reified T, reified R> transform(
value: T,
transformFunc: (T) -> R
): Pair<R, String> {
val result = transformFunc(value)
val fromType = T::class.simpleName
val toType = R::class.simpleName
return result to "转换自: $fromType 到: $toType"
}
// 使用
val (result, info) = transform(123) { it.toString() }
println("$result, $info") // 123, 转换自: Int 到: String
带约束的具体化类型
inline fun <reified T : Number> sumList(list: List<Any>): T {
return list
.filterIsInstance<T>() // 过滤出 T 类型的元素
.reduce { acc, num ->
when (T::class) {
Int::class -> (acc as Int + num as Int) as T
Double::class -> (acc as Double + num as Double) as T
else -> throw IllegalArgumentException("不支持的数值类型")
}
}
}
// 使用
val intSum: Int = sumList(listOf(1, 2, 3, "ignored", 4.5))
println(intSum) // 6 (只计算整数)
6. 限制和注意事项
限制 1:不能用于非内联函数
// 错误!reified 必须与 inline 一起使用
// fun <reified T> regularFunction() { }
限制 2:不能用于类的类型参数
// 错误!reified 不能用于类
// class Box<reified T>(val value: T)
限制 3:内联函数的性能考虑
由于 reified 必须与 inline 函数一起使用,需要考虑内联带来的影响:
- 优点:消除函数调用开销,性能更好
- 缺点:如果函数体很大,可能导致生成的字节码膨胀
限制 4:某些情况下无法内联
inline fun <reified T> process(crossinline callback: () -> Unit) {
// crossinline 标记的 lambda 不能直接内联
runOnUiThread { callback() }
// 但类型 T 仍然可用
println("处理类型: ${T::class.simpleName}")
}
7. 与 Java 互操作
从 Java 调用
// Java 代码中无法调用 reified 函数
// KotlinUtils.<String>isTypeOf("test"); // 编译错误
如果需要从 Java 调用,可以创建一个非 reified 的包装函数:
// Kotlin
inline fun <reified T> isTypeOf(value: Any): Boolean = value is T
@JvmStatic
fun isString(value: Any): Boolean = isTypeOf<String>(value)
// Java 中调用
boolean result = KotlinUtils.isString("test");
8. 替代方案对比
| 方法 | 优点 | 缺点 |
|---|---|---|
reified | 类型安全,简洁 | 必须内联,Java 无法调用 |
传递 KClass 参数 | 兼容 Java,更灵活 | 代码冗长,类型不安全 |
TypeToken (Gson 风格) | 解决复杂泛型 | 冗长,性能开销 |
9. 最佳实践
- 小函数使用:
reified最适合小的工具函数 - 避免滥用:只在真正需要类型信息时使用
- 考虑性能:大的函数体可能不适合内联
- 提供 Java 兼容版本:如果需要 Java 调用
// 最佳实践示例
inline fun <reified T : Any> Gson.fromJsonSafe(json: String): Result<T> {
return try {
Result.success(fromJson<T>(json))
} catch (e: Exception) {
Result.failure(e)
}
}
// Java 兼容版本
fun <T : Any> Gson.fromJsonSafe(
json: String,
type: Class<T>
): Result<T> {
return try {
Result.success(fromJson(json, type))
} catch (e: Exception) {
Result.failure(e)
}
}
总结
reified 关键字是 Kotlin 中解决类型擦除问题的优雅方案,它使得泛型类型在运行时可用。虽然有一些限制(必须内联、不能用于类等),但在合适的场景下,它能极大地简化代码并提高类型安全性。