Kotlin泛型协变问题:save方法类型不匹配如何解决?
Kotlin泛型Repository调用save方法类型不匹配问题解决
问题场景
现有如下代码结构:
- 两个实现
Data接口的类:
interface Data class A : Data class B : Data
- 定义泛型仓库接口
TestRepository:
interface TestRepository<T : Data> { fun save(data: T): T }
- 实现对应具体仓库类:
@Repository class ARepository : TestRepository<A> { override fun save(data: A): A { // 实现逻辑 return data } } @Repository class BRepository : TestRepository<B> { override fun save(data: B): B { // 实现逻辑 return data } }
- 服务类
CService根据参数返回对应仓库,返回类型声明为TestRepository<out Data>:
@Service class CService( private val aRepository: ARepository, private val bRepository: BRepository ) { fun test(t: String): TestRepository<out Data> = when (t) { "1" -> aRepository "2" -> bRepository else -> throw Error("error") } }
调用时出现类型不匹配错误:
Type mismatch. Required: Nothing Found: Data
错误原因
TestRepository<out Data>使用了协变(out)标记,协变意味着该类型只能作为生产者(对外输出Data类型数据,比如读取方法),不能作为消费者(接收Data类型数据,比如save方法的参数)。
Kotlin编译器会将协变类型的输入参数类型擦除为Nothing(因为无法保证传入的Data子类能被具体仓库接受),所以调用save(Data)时会触发类型不匹配错误。
解决方案
方案1:避免直接返回协变类型,在服务内部封装save逻辑
不要让调用方直接获取仓库实例,而是在CService中提供统一的save方法,内部根据参数路由到对应仓库:
@Service class CService( private val aRepository: ARepository, private val bRepository: BRepository ) { fun save(t: String, data: Data): Data { return when (t) { "1" -> aRepository.save(data as A) "2" -> bRepository.save(data as B) else -> throw Error("error") } } }
注意:这种方式需要保证传入的
data类型与参数t对应,否则会抛出类型转换异常,可根据业务场景添加校验逻辑。
方案2:使用泛型方法+具体化类型,让调用方明确传入类型
通过reified关键字实现具体化泛型类型,无需字符串参数即可匹配对应仓库:
@Service class CService( private val aRepository: ARepository, private val bRepository: BRepository ) { inline fun <reified T : Data> save(data: T): T { return when (T::class) { A::class -> aRepository.save(data as A) as T B::class -> bRepository.save(data as B) as T else -> throw Error("不支持的类型") } } }
调用方式:
val cService = CService(ARepository(), BRepository()) cService.save(A()) // 自动匹配ARepository cService.save(B()) // 自动匹配BRepository
方案3:调整接口设计,拆分读写职责
将TestRepository拆分为只读和只写接口,根据场景返回对应类型:
// 只读接口(协变,支持输出) interface ReadRepository<out T : Data> { // 示例读取方法 fun findById(id: String): T? } // 只写接口(逆变,支持输入) interface WriteRepository<in T : Data> { fun save(data: T): T } // 完整仓库接口 interface TestRepository<T : Data> : ReadRepository<T>, WriteRepository<T> // 具体仓库实现不变 @Repository class ARepository : TestRepository<A> { override fun save(data: A): A = data override fun findById(id: String): A? = null } @Repository class BRepository : TestRepository<B> { override fun save(data: B): B = data override fun findById(id: String): B? = null }
如果业务只需要读取操作,CService可以返回ReadRepository<out Data>;如果需要写入,必须明确具体类型(比如WriteRepository<A>),避免协变导致的写入限制。
方案4:调用方明确指定具体仓库类型
如果调用方清楚参数对应的具体仓库类型,可以显式指定类型转换:
val cService = CService(ARepository(), BRepository()) val repo = cService.test("1") as TestRepository<A> repo.save(A()) // 正常调用
这种方式简单但不够安全,若参数与类型不匹配会抛出转换异常,适合明确业务场景的小规模代码。
内容的提问来源于stack exchange,提问作者yacho
相关产品推荐
相关产品推荐

