接口与泛型类型不匹配问题:排查及解决咨询
摘要
- 使用泛型与星型投影时出现类型不匹配
- 所需类型本身也是泛型(嵌套泛型场景)
- 附可复现代码
问题详情
我需要在不同自定义类型中复用并扩展接口及其函数,已经把接口重构为泛型版本,但现在无法调用更新后的函数。
我希望能给sorter设置不同的选项/策略(切换不同类型/子类),不想硬编码类型——那样就得为每种类型创建对应对象,太不灵活。
当前的难点在于目标类型本身就是泛型interface Repo<T>:子类实现该接口后,系统识别的是子类类型,但函数要求的是接口类型。因为多了一层泛型,我不确定该用哪种类型、怎么用。
我猜过要在Repo里硬编码泛型参数T,但那样就没法灵活设置排序器策略了。有没有其他可行方案?
最小可复现代码
interface Repo<T> { fun findAll(): Array<T> } data class CustomRepo(val num: Int) : Repo<String> { override fun findAll(): Array<String> { return arrayOf("Sorted1, Sorted2, $num") } } class SortOrderObject : SortOrder<Repo<Any>> { override fun calcSort(repo: Repo<Any>): String { val total = repo.findAll() return total.toString() } } interface SortOrder<Repo> { fun calcSort(repo: Repo): String } fun main() { val sorter: SortOrder<Repo<Any>> = SortOrderObject() val orderString = sorter.calcSort(CustomRepo(3)) // Type mismatch: inferred type is CustomRepo but Repo<Any> was expected println(orderString) }
核心问题分析
CustomRepo实现的是Repo<String>,而SortOrderObject要求的是Repo<Any>——Kotlin中泛型默认是不变型的,Repo<String>并不是Repo<Any>的子类,因此会出现类型不匹配。
方案1:让SortOrder支持协变泛型(推荐,兼顾灵活性)
修改SortOrder接口,给泛型参数加上out关键字开启协变,同时用星投影Repo<*>匹配任意Repo实现:
interface SortOrder<out R> { fun calcSort(repo: R): String } class SortOrderObject : SortOrder<Repo<*>> { override fun calcSort(repo: Repo<*>): String { val total = repo.findAll() return total.contentToString() // 用contentToString()输出更易读的数组内容 } } fun main() { val sorter: SortOrder<Repo<*>> = SortOrderObject() val orderString = sorter.calcSort(CustomRepo(3)) println(orderString) }
这种方式下,不管Repo的泛型参数是什么类型,SortOrderObject都能处理,同时保留了策略切换的灵活性——你可以创建其他SortOrder实现类来对应不同排序逻辑。
方案2:给SortOrder设置双层泛型(适合需要明确泛型关联的场景)
如果需要在SortOrder中明确依赖Repo的泛型类型,可以将SortOrder定义为双层泛型:
interface SortOrder<T, R : Repo<T>> { fun calcSort(repo: R): String } class SortOrderObject : SortOrder<Any, Repo<Any>> { override fun calcSort(repo: Repo<Any>): String { val total = repo.findAll() return total.contentToString() } } fun main() { val sorter = SortOrderObject() // 类型转换兼容Repo<String>,或直接将SortOrderObject泛型改为<String, Repo<String>> val orderString = sorter.calcSort(CustomRepo(3) as Repo<Any>) println(orderString) }
该方案灵活性稍弱,更适合需要明确泛型类型约束的场景。
方案3:使用逆变泛型(适合特定类型场景)
如果只需要处理Repo<String>及其子类,可以给Repo的泛型参数加上in关键字开启逆变:
class SortOrderObject : SortOrder<Repo<in String>> { override fun calcSort(repo: Repo<in String>): String { val total = repo.findAll() return total.contentToString() } } fun main() { val sorter: SortOrder<Repo<in String>> = SortOrderObject() val orderString = sorter.calcSort(CustomRepo(3)) println(orderString) }
该方案针对性强,适合只处理特定Repo子类的场景。
内容的提问来源于stack exchange,提问作者BoydyBoydy

