Вопрос об общих свойствах Kotlin
У меня возникли некоторые проблемы с Kotlin при переводе моего проекта Android из Java в Kotlin. Скажем, у меня есть интерфейс I и интерфейс O, который расширяет интерфейс I.
interface I{
}
interface O: I{
}
И универсальный класс A, который имеет универсальный параметр V, который расширяет interfaceI, и универсальный класс B, который расширяет класс A:
abstract class A<V: I> {
}
class B : A<O>() {
}
Когда я пытаюсь создать такую собственность:
val returnB: A<I>
get() = b
Я получаю сообщение об ошибке компилятора "требуется A, найдено B". В Java это будет работать без проблем. Как я могу получить доступ к этому с помощью Kotlin?
Мне нужно использовать этот подход для базовых классов в моем приложении.
BaseViewModel, у которого есть универсальный параметр для класса Navigator:
abstract class BaseViewModel<N>(application: Application, val repositoryProvider:
RepositoryProvider) : AndroidViewModel(application) {
var navigator: N? = null
fun onDestroyView() {
navigator = null
}
open fun onViewAttached() {
}
}
Класс BaseActivity:
abstract class BaseActivity<T : ViewDataBinding, V : BaseViewModel<BaseNavigator>> : AppCompatActivity(),
BaseFragment.Callback, BaseNavigator {
// .......
private var mViewModel: V? = null
/**
* Override for set view model
* @return view model instance
*/
abstract val viewModel: V
// .......
}
Интерфейс BaseNavigator использует для ВМ - Просмотр связи:
interface BaseNavigator {
fun invokeIntent(intent: Intent?, b: Bundle?, c: Class<*>?,
forResult: Boolean, requestCode: Int)
fun replaceFragment(fragment: Fragment, addToBackStack: Boolean)
fun showDialogFragment(fragment: DialogFragment?, tag: String?)
fun showToast(message: String?)
}
Вот пример кода, где я расширяю эти классы:
AuthViewModel:
class AuthViewModel(context: Application, repositoryProvider: RepositoryProvider) :
BaseViewModel<AuthNavigator>(context,repositoryProvider) {
// ....
}
AuthNavigator:
interface AuthNavigator : BaseNavigator {
fun requestGoogleAuth(code: Int)
fun requestFacebookAuth(callback: FacebookCallback<LoginResult>)
}
И класс AuthActivity, где появилась ошибка:
class AuthActivity : BaseActivity<ActivityAuthBinding, BaseViewModel<BaseNavagator>>(),
GoogleApiClient.OnConnectionFailedListener, AuthNavigator {
@Inject
lateinit var mViewModel: AuthViewModel
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel // Required:BaseViewModel<BaseNavigator> Found: AuthViewModel
}
Я также пытался изменить общий параметр в AuthActivity с BaseViewModel на AuthViewModel, но компилятор выдает ошибку "Требуется BaseViewModel".
And i tried to change
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel
to
override val viewModel: AuthViewModel
get() = mViewModel
но в этом случае компилятор выдает ошибку "Тип свойства -" AuthViewModel ", который не является переопределенным типом подтипа".
обновление: это работает, когда я добавляю свойство в BaseViewModel:
BaseViewModel<out N : BaseNavigator>
Но в этом случае я могу только создать
private var navigator: N? = null
который я должен быть публичным, чтобы я мог установить его в классе Activity. Могу ли я создать публичный сеттер для этого свойства? Когда я пытаюсь создать сеттер, возникает ошибка:
private var navigator: N? = null
fun setNavigator(n: N) { // error: Type parameter N is declared as 'out' but occurs in 'in' position in type N
navigator = n
}
1 ответ
Похоже, вы ожидаете, что параметр типа будет вести себя ковариантно. Kotlin использует декларацию-сайт дисперсии. If you do not specify the variance, generic type parameters are invariant.
In other words, right now there is no relationship between A<I>
а также A<O>
, But if you declare
abstract class A<out V : I>
затем A<O>
это подтип A<I>
,
(Существует также <in>
for contravariance, which works the other way around. See https://kotlinlang.org/docs/reference/generics.html for more details.)