Kotlin泛型Foo类型不匹配报错原因及解决方案咨询
解读Kotlin泛型类型不匹配问题
先把你的原始代码贴出来,方便咱们一步步分析:
class Foo<T>(val t : T?) fun <T : Any, R : Any> Foo<T?>.transform(transformer : (T) -> R) : Foo<R?> { return when (t) { null -> Foo(null) else -> Foo(transformer(t)) } } fun main(args : Array<String>) { val foo = Foo(args.firstOrNull()) // 这里的类型推断是关键! val bar = foo.transform<String, Int> { t -> t.length } // 报错:Type mismatch. Required: Foo<String?> Found: Foo<String> val baz = bar.transform<Int, IntRange> { t -> t..(t + 1) } }
第一个错误:为什么Foo<String>不能赋值给Foo<String?>?
你可能会疑惑:String明明能安全转成String?,为啥它们的容器Foo<String>和Foo<String?>就不兼容?
关键点有两个:
- 类型推断的小陷阱:
Foo(args.firstOrNull())里,args.firstOrNull()返回String?,而Foo的构造参数是T?。编译器会自动推断T为String(因为String?完全符合T?的定义),所以foo的实际类型是Foo<String>,不是你以为的Foo<String?>。 - 泛型的不变性:Kotlin默认的泛型类是不变的——哪怕
A是B的子类型,Foo<A>和Foo<B>也是完全独立的类型,不能互相赋值。所以当你调用foo.transform<String, Int>时,扩展函数要求接收者是Foo<String?>,但实际传入的是Foo<String>,自然就报类型不匹配了。
第二个错误:移除?后的类型冲突
当你把扩展函数改成Foo<T>.transform,接收者变成了Foo<T>。此时bar的类型是Foo<Int?>(来自第一个transform的返回值Foo<R?>),同样因为泛型不变性,Foo<Int?>不能赋值给Foo<Int>,所以报错Type mismatch. Required: Foo<Int> Found: Foo<Int?>,这个正如你所说,很好理解——Int?不能直接转成Int,它们的容器自然也不兼容。
解决方案一:给Foo添加协变修饰符out
把Foo类修改成协变泛型:
class Foo<out T>(val t : T?)
out T表示Foo是协变的,意思是如果A是B的子类型,那么Foo<A>可以安全赋值给Foo<B>。这样:
Foo<String>就能转成Foo<String?>(因为String是String?的子类型),直接解决第一个错误;- 编译器还能自动推断
transform的类型参数,你甚至不用手动写foo.transform<String, Int>,直接foo.transform { t -> t.length }就行。
解决方案二:引入第三个泛型参数U
另一种思路是修改扩展函数,添加第三个类型参数来兼容T和T?:
fun <T : Any, U : T?, R : Any> Foo<U>.transform(transformer : (T) -> R) : Foo<R?> { return when (val t = this.t) { null -> Foo(null) else -> Foo(transformer(t as T)) // 这里需要显式转换,编译器无法智能识别非空 } }
这个方案让接收者类型U同时兼容T和T?,所以Foo<String>和Foo<String?>都能匹配。但它有两个明显的缺点:
- 调用规则怪异:第一次调用
foo.transform编译器能自动推断,但第二次bar.transform必须显式指定所有类型参数(比如bar.transform<Int, Int?, IntRange>)才能生效; - 丢失智能转换:因为
U是T?,哪怕你用when判断了t非空,编译器也无法自动把t转成T,必须手动用as T或者!!处理。
内容的提问来源于stack exchange,提问作者user2805089
相关产品推荐
相关产品推荐

