Kotlin 中的 Reified — 是什么、语法和应用

作者: IT Sectr 发布日期: 2026-06-21 阅读时间: 8 分钟

reified — Kotlin 语言中的关键字,允许在内联函数中访问泛型参数的类型。在普通的泛型中,类型擦除(type erasure)会起作用——类型信息在编译阶段被擦除,但 reified 可以保留它。根据 Kotlin Documentation, 2025,reified 仅在内联函数中工作,因为编译器在内联阶段替换实际类型

要点

  • reified — 泛型参数的修饰符,在运行时保留类型信息
  • 仅限内联 — reified 仅在内联函数内部工作
  • 类型擦除 — Java/Kotlin 的标准机制,擦除泛型类型;reified 绕过它
  • is 检查 — 可能:if (value is T) 替代 if (value is String)
  • 创建实例 — 无需传递 Class 即可创建 T::class.java.newInstance()

Kotlin 中的 Reified 是什么?

reified — 是内联函数泛型参数的修饰符,它使类型在运行时变为真实(reify 意为“具体化”)。没有 reified,泛型函数内部的类型 T 不可访问——编译器应用类型擦除,擦除所有关于类型的信息。reified 强制编译器在调用位置替换具体类型,使其通过 T::class 和 is 运算符可访问。

根据 Kotlin Survey by Kodee (2024),reified 类型参数是 Kotlin 最受欢迎的十大功能之一——52% 的受访开发人员使用它,主要用于编写泛型工厂、DI 容器和序列化器。Reified 与 Gson、Moshi 和 Kotlinx Serialization 结合使用时特别流行。

从技术上讲,其机制很简单:当调用带有 reified 参数的内联函数时,编译器知道参数的具体类型(Int、String、User),并将其替换到 T 的位置。在字节码中,reified 参数转换为普通的 Class,作为隐藏参数传递。

使用 reified 编写需要在运行时获取类型的泛型函数——创建实例、类型检查、获取 Class 用于反射或序列化。

泛型中的类型擦除问题

类型擦除(Type erasure)——Java 和 Kotlin 的机制,在编译期间泛型参数的信息被擦除。在字节码中,List 和 List 就变成了 List。是为了向后兼容没有泛型的 Java 1.4 而做的,但在运行时处理类型时造成了限制。

kotlin
// ❌ 错误:无法检查已擦除类型的实例
fun <T> checkType(value: Any) {
    if (value is T) { // 类型擦除 — T 未知
        println("类型匹配")
    }
}

// ✅ Solution: pass Class as parameter
fun <T> checkTypeWithClass(
    value: Any,
    clazz: Class<T>
) {
    if (clazz.isInstance(value)) {
        println("类型匹配")
    }
}

在示例中,checkType 由于类型擦除而无法编译——编译器不知道用什么类型替换 T。在 checkTypeWithClass 中,问题通过显式传递 Class 解决,但这需要样板代码:每次调用都附带 .java 或 ::class.java。Reified 完全消除了这个样板代码。

Reified 的语法和工作机制

reified 修饰符放在内联函数中的泛型参数之前。函数必须是内联的——编译器必须能够在内联阶段替换具体类型。

kotlin
inline fun <reified T> isA(value: Any): Boolean {
    return value is T
}

fun main() {
    println(isA<String>("你好")) // true
    println(isA<Int>("你好"))  // false
}

在编译期间,调用 isA("Hello") 被替换为 value is String 检查。调用 isA("Hello") — 被替换为 value is Int。类型被字面替换,从而可以使用 is、as、::class 和其他在类型擦除时不可用的操作。

Reified 函数的反编译

如果我们反编译 isA("Hello") 的字节码,IntelliJ IDEA 将在 Java 中显示大致如下的结果:String.class.isInstance(value)。编译器替换了具体的 java.lang.String.class 而不是泛型参数——没有通过名称搜索类型的反射,只有对类的直接引用。

使用 Reified 进行类型检查:is 和 as

Reified 最常见的应用——通过 is 运算符进行类型检查。在普通的泛型函数中,value is T 无法编译。使用 reified,它就像普通类一样工作:value is String、value is List(几乎——考虑到 reified 对参数化类型的限制)。

kotlin
inline fun <reified T> List<Any>.filterByType(): List<T> {
    return this.filter { it is T }.map { it as T }
}

val mixed = listOf("a", 1, "b", 2)
val strings = mixed.filterByType<String>() // ["a", "b"]
val ints = mixed.filterByType<Int>()    // [1, 2]

扩展函数 filterByType 过滤列表,只留下指定类型的元素。没有 reified,必须编写带 Class 参数的 filterByType(list)。使用 reified,调用读起来像是对列表的自然操作,提高了数据处理链的可读性。

根据数据 Kotlin Coroutines Guide (JetBrains, 2025),reified 类型检查用于 launch 和 async 中传递协程结果的类型,这在大多数情况下避免了显式指定类型。

使用 Reified 进行反射:创建实例和访问 Class

Reified 提供对 T::class 的访问——对 KClass 的引用,通过 .java 可以获取 Java Class。这为通过反射创建实例、使用序列化器和在运行时获取类注解提供了可能性。

kotlin
inline fun <reified T> createInstance(): T =
    T::class.java.getDeclaredConstructor().newInstance()

// 用法
data class User(val name: String = "default")
val user = createInstance<User>()

// 使用 Gson 进行序列化
inline fun <reified T> Gson.fromJson(json: String): T =
    this.fromJson(json, T::class.java)

// 获取注解
inline fun <reified T> hasAnnotation<A>(): Boolean where A : Annotation =
    T::class.java.isAnnotationPresent(A::class.java)

Gson 的 fromJson 包装器——在生产中使用 reified 的经典示例。可以写 gson.fromJson(json) 代替 gson.fromJson(json, User::class.java)。这看起来是一个小改进,但在有数百个序列化调用的项目中,reified 显著减少了样板代码并使代码更清晰。

Reified 的限制和替代方案

Reified 有局限性。第一——仅在内联函数内部工作。如果函数不能做成内联的(例如,它是递归的或太大),reified 不可用。第二——reified 不能直接与挂起函数一起使用,只能通过内联包装器。

第三——reified 不能完全与参数化类型一起工作。例如,filterByType>() 可能产生意外结果,因为对于参数化类型,reified 只保留原始类型(List),而不指定泛型参数。要完全检查参数化类型,需要使用 TypeToken 进行反射。

操作使用 Reified不使用 Reified
value is T✅ 有效❌ 编译错误
T::class✅ 有效❌ 编译错误
List is T⚠️ 仅原始类型❌ 错误
创建实例✅ 通过反射❌ 需要 Class
挂起函数❌ 仅通过内联包装器❌ 不适用

对于 reified 不可用的场景,使用显式传递 Class 或来自库的 TypeToken 的模式(例如,Gson TypeToken 或 Jackson TypeReference)。这种方法在任何函数中都有效,但需要样板代码且不太方便。

常见问题

为什么 reified 只与内联函数一起工作?

编译器在嵌入函数体时将 reified 参数 T 替换为具体类型。如果函数不是内联的,编译器没有地方替换类型——泛型函数的调用通过统一的字节码进行,其中 T 被擦除了。内联为每个类型参数创建单独的字节码副本。

可以声明 reified 属性吗?

不能,reified 仅适用于函数参数。对于属性,使用带有返回值的 inline fun 模式,或通过构造函数显式传递 Class。扩展属性也不支持 reified。

Reified 如何处理可空类型?

Reified 支持可空类型:reified T : Any(非空)和简单的 reified T(可以为空)。对于可空类型,T::class 返回非空版本的类(String? 返回 String::class)。value is T 检查考虑了 null:如果 T = String?,那么 null is T = true。

Reified 有性能开销吗?

最小。Reified 不使用反射——编译器在内联阶段替换具体类型。在字节码中,这是对类的直接引用(ldc + checkcast/invokevirtual)。与手动传递 Class 相比没有开销——两种变体生成相同的字节码。

可以在 Android 开发中使用 reified 吗?

可以,reified 在 Android 中被积极使用。Bundle.getParcelable()、Intent.getSerializableExtra()、来自 Android KTX 的 viewModels()——所有这些函数都使用 reified 来避免显式传递 Class根据数据 Google Android Docs (2025),reified 被推荐用于需要在运行时获取类型的泛型 API。

总结

  • reified — 内联函数泛型参数的修饰符,在运行时保留类型
  • 类型擦除 — 擦除类型的标准机制;reified 通过内联绕过它
  • is/as — 类型检查和转换使用 reified 像普通类一样工作
  • 类引用 — T::class 和 T::class.java 可用于反射和序列化
  • 仅限内联 — 由于类型替换机制,没有内联函数 reified 不可能实现
  • 参数化类型 — reified 不保留泛型参数(仅原始类型)
  • 应用 — Gson/Moshi 序列化、DI 容器、集合中的类型检查、Android KTX

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读