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
相关产品推荐
相关产品推荐

