You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin泛型示例中为何必须指定out协变关键字

问题场景

你提到的示例代码如下:

open class Case<T> {}
class InheritedCase: Case<Nothing>()
fun getCase(): Case<out String> = InheritedCase()

要解释为什么必须加out关键字、直接声明返回Case<String>会触发编译错误,核心要理解Kotlin的两个基础规则:

  • Kotlin泛型默认是不变的:就算类型A是类型B的子类型,未显式声明型变的前提下,Case<A>和Case<B>不存在继承关系,不能互相赋值,只有声明协变(out)或逆变(in)时,才会放开对应方向的类型转换。
  • Kotlin中Nothing是所有类型的子类型,该类型没有任何实际实例,仅用来标记“永远不会产生返回值”的逻辑分支。

无out时编译报错的原因

InheritedCase的实际静态类型是Case<Nothing>,如果把函数返回值声明为Case<String>,首先会触发默认不变规则的类型校验:Case<Nothing>和Case<String>是完全无关的两个类型,本身就不满足赋值匹配要求。
更深层的原因是类型安全限制:如果允许这种赋值,调用方拿到返回的Case<String>后,理论上可以调用类中参数类型为T(也就是String)的写入方法,往实例里存入一个String对象,但实际返回的是Case<Nothing>实例——Nothing没有任何合法实例,存入String会直接打破类型系统的安全保证,编译器必然会拦截这种写法。

加out后编译通过的原因

当返回值声明为Case<out String>时,相当于给编译器明确了一个使用约束:这个返回值的泛型参数是协变的,后续使用时只会从中取出String类型的值,绝对不会调用任何传入T类型参数的写入方法。
这时候协变规则生效,泛型参数的子类型关系会被保留:因为Nothing是String的子类型,Case<Nothing>自然满足Case<out String>的约束——毕竟从实例里取出来的Nothing值本来就可以被向上转型为任意类型(包括String),全程不会有类型安全风险,所以编译可以正常通过。

内容的提问来源于stack exchange,提问作者Vlad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 12:24:57