如何解决Kotlin泛型类型变异约束导致的类型不匹配问题?
刚接触Kotlin,泛型相关知识有些困惑,搭建了如下架构:
- 若干数据类
- 用于处理数据的泛型接口
- 针对每种数据类型的处理接口实现类
- 包含待处理数据及对应处理器的泛型处理任务类
- 实现处理接口的全局单例处理器,接收处理任务并将处理逻辑委托给任务自带的处理器,完全不关心数据本身。
简化后的代码:
class DataOne class DataTwo interface DataProcessor<in T> { fun process(o: T) } class DataOneProcessor: DataProcessor<DataOne> { override fun process(o: DataOne) = println("Processing DataOne") } class DataTwoProcessor: DataProcessor<DataTwo> { override fun process(o: DataTwo) = println("Processing DataTwo") } class ProcessingJob<T>(val data: T, val processor: DataProcessor<T>) object GlobalProcessor: DataProcessor<ProcessingJob<Any>> { override fun process(job: ProcessingJob<Any>) = job.processor.process(job.data) } fun main() { GlobalProcessor.process(ProcessingJob(DataOne(), DataOneProcessor())) }
编译时出现错误:
Type mismatch.
Required: ProcessingJob
Found: ProcessingJob
已知错误原因:将DataOne类型的DataProcessor视为Any类型的DataProcessor时,可能会被要求处理DataTwo类型的数据,出于类型安全考虑,编译器禁止这种操作。
方法一:使用通配符适配任意ProcessingJob类型
修改GlobalProcessor的泛型参数为ProcessingJob<*>,通过安全的类型转换完成调用:
object GlobalProcessor: DataProcessor<ProcessingJob<*>> { override fun process(job: ProcessingJob<*>) { @Suppress("UNCHECKED_CAST") (job.processor as DataProcessor<Any>).process(job.data) } }
这里的*通配符表示接受任意类型参数的ProcessingJob,由于我们明确知道每个ProcessingJob的processor必然能处理自身的data,因此类型转换是安全的,添加@Suppress("UNCHECKED_CAST")消除编译器警告即可。
方法二:给GlobalProcessor定义泛型处理方法
放弃让GlobalProcessor实现DataProcessor接口,直接定义泛型的process方法,让编译器自动推导类型:
object GlobalProcessor { fun <T> process(job: ProcessingJob<T>) { job.processor.process(job.data) } }
这种方案最简洁,完全符合类型安全要求,无需任何转换,编译器会在调用时自动识别T的具体类型。
方法三:让ProcessingJob支持协变
修改ProcessingJob的泛型声明为out T,使ProcessingJob<DataOne>可以被视为ProcessingJob<Any>的子类型:
class ProcessingJob<out T>(val data: T, val processor: DataProcessor<T>) object GlobalProcessor: DataProcessor<ProcessingJob<Any>> { override fun process(job: ProcessingJob<Any>) { job.processor.process(job.data) } }
协变out T要求T仅作为输出(比如data是只读属性,这里满足条件),而DataProcessor<in T>是逆变类型,两者组合后ProcessingJob<out T>的类型关系合法,此时ProcessingJob<DataOne>可以向上转型为ProcessingJob<Any>,从而通过编译。
内容的提问来源于stack exchange,提问作者Cosmin Radu

