2-2-13 快速掌握Kotlin-reified关键字

38 阅读2分钟

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. 最佳实践

  1. 小函数使用reified 最适合小的工具函数
  2. 避免滥用:只在真正需要类型信息时使用
  3. 考虑性能:大的函数体可能不适合内联
  4. 提供 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 中解决类型擦除问题的优雅方案,它使得泛型类型在运行时可用。虽然有一些限制(必须内联、不能用于类等),但在合适的场景下,它能极大地简化代码并提高类型安全性。