Dagger 和 Kotlin - 将 class 绑定到其通用超类型的问题

Dagger and Kotlin - Issue with binding a class to its generic supertype

我现在正在用头撞墙,因为我想不通。 我有一个名为 Mapper 的通用接口,它有两个通用类型参数。现在我想利用多重绑定并将此接口的多个实现绑定到类型 Map<Class<out Any>, Provider<Mapper<Any, Any>> 的映射中。我的代码如下所示:

interface Mapper<DTO, Entity> {
    
    fun toEntity(model: DTO): Entity
    
    fun toDto(model: Entity): DTO
    
}


class PersistedIntakeEntryMapper @Inject constructor() : Mapper<PersistedIntakeEntry, IntakeEntry> {

    override fun toEntity(model: PersistedIntakeEntry): IntakeEntry { TODO() }

    override fun toDto(model: IntakeEntry): PersistedIntakeEntry { TODO() }

}

@Module
interface MapperModule {

    @Binds
    @IntoMap
    @MapperKey(PersistedIntakeEntry::class)
    @ModelMappers
    fun bindPersistedIntakeEntryMapper(mapper: PersistedIntakeEntryMapper): Mapper<Any, Any>

}

@Singleton
class MapperFactory @Inject constructor(
    @ModelMappers val mappers: Map<Class<out Any>, @JvmSuppressWildcards Provider<Mapper<Any, Any>>>,
) {

    @Suppress("UNCHECKED_CAST")
    inline fun <reified DTO: Any, Entity> get(): Mapper<DTO, Entity>? {
        TODO()
    }

}

Dagger 特别抱怨 PersistedIntakeEntryMapper 不能分配给 Mapper<Any, Any>MapperModule.java:13: error: @Binds methods' parameter type must be assignable to the return type.
然而:奇怪的是我对另一个组件有相同的设置,它就像一个魅力:

interface ViewModelFactory<VM : ViewModel, SavedState, Parameters> {

   fun create(savedState: SavedState?, parameters: Parameters?): VM

}


class SetCalorieGoalViewModelFactory @Inject constructor(
    private val getCalorieGoalUseCase: GetCalorieGoalUseCase,
    private val setCalorieGoalUseCase: SetCalorieGoalUseCase,
    private val navigator: Navigator,
) : ViewModelFactory<SetCalorieGoalViewModel, SetCalorieGoalUiState, Nothing> {

    override fun create(savedState: SetCalorieGoalUiState?, parameters: Nothing?): SetCalorieGoalViewModel {
        TODO()
    }

}


@Module
interface SetCalorieGoalUiModule {

    @Binds
    @IntoMap
    @ViewModelKey(SetCalorieGoalViewModel::class)
    fun bindSetCalorieGoalViewModelFactory(factory: SetCalorieGoalViewModelFactory)
            : ViewModelFactory<ViewModel, Any, Any>

}

我可以毫无问题地将 SetCalorieGoalViewModelFactory 绑定到 ViewModelFactory<SetCalorieGoalViewModel, Any, Any> 类型。使其中一个工作而另一个不工作的这些设置之间有什么区别?我一辈子都弄不明白。非常感谢任何试图解决这个问题的人。

首先,查看 kotlin documentation 通用方差主题以及相关的 java 主题(因为匕首生成 java 代码)。

一般来说,问题是 Mapper<PersistedIntakeEntry, IntakeEntry>Mapper<Any, Any> 是不变的,这意味着一个不是另一个的子类型。基本上这个赋值 val mapper: Mapper<Any, Any> = PersistedIntakeEntryMapper() 不会编译,这就是 dagger 告诉你的。这是有道理的,因为 Mapper<Any, Any> 必须能够将 Any 映射到 AnyPersistedIntakeEntryMapper 显然不是这种情况 - 它期望 PersistedIntakeEntryIntakeEntry.

按照上面的文档,如果你的声明有像 interface Mapper<out DTO, out Entity> 这样指定的 out 修饰符,那将是可能的,但这在你的情况下不起作用,因为你的类型参数在 in 个职位。

有趣的问题是为什么它适用于 ViewModelFactory。这似乎是 KAPT 中的一个错误,它只是在看到 Nothing 时在生成的代码中省略了泛型类型参数。它使它绕过编译器检查(但在运行时使用它并不安全!),因为泛型主要是编译时的东西(参见 java 中的 type erasure ).