У меня возникла проблема с параметрами функций в Kotlin. Я объясню проблему с помощью некоторого кода.
Я создал иерархию классов. Когда я передаю подтип в функцию, ожидающую родительский тип, проблем нет.
open class A (val i: Int)
class B (val j: Int) : A(j)
fun f(x: A){
print(x)
}
fun test_f(){
f(A(1))
f(B(1)) //no problem
}
Я попытался имитировать это с помощью параметров функции.
fun g(x: (A)->Int){
print(x)
}
fun test_g(){
val l1 = { a: A -> a.hashCode()}
g(l1)
val l2 = { b: B -> b.hashCode()}
g(l2) //Error: Type mismatch. Required: (A)->Int, Found: (B)->Int
}
Кажется, что тип функции (B) -> Int
не является подтипом (A) -> Int
.
Каков наилучший способ решить эту проблему?
Моя изначальная проблема должен определить функцию более высокого порядка в A.h
, которая принимает функцию z: (A) -> X
в качестве параметра. И я хочу вызвать h
объект типа B
и передать функцию z: (B) -> X
.
Обновлять: Я пробовал дженерики с верхней границей, но моя проблема не решена. Пожалуйста, найдите код ниже:
// Using generics doesn't allow me to pass A.
open class A (val i: Int) {
fun <M: A> g(x: (M)->Int){
print(x(this)) // Error: Type mismatch. Expected: M, Found: A
}
}
Вы можете решить эту проблему, используя дженерики и функция расширения на универсальном приемнике. Получение функции расширения из вашего обновленного образца:
fun <T : A> T.g(x: (T)->Int){
print(x(this))
}
Таким образом гарантируется, что получатель и первый тип параметра данной функции совпадают, что является либо A
, либо его подтипом.
@Neo обновил ответ ... в основном показывая, как вы можете решить свой обновленный образец с помощью универсальной функции на универсальном приемнике ...
Да, это именно то, что я искал! Аккуратный подход.
Чтобы помочь другим взглянуть на это: важное различие между кодом, который я разместил в своем обновлении, и ответом @Roland - это часть "T.g", которая позволяет функции быть функцией расширения для универсального типа T.
То, что вы пытаетесь сделать, — это преобразование из типа функции (B) -> Int
(источник) в (A) -> Int
(цель). Это не безопасное преобразование.
Ваша исходная функция (B) -> Int
принимает любой экземпляр, который является B
, но не обязательно экземпляр типа A
. Более конкретно, он не может обрабатывать все аргументы типа A
, но не типа B
.
Представьте, что ваши классы выглядят так:
open class A
class B : A {
fun onlyB() = 29
}
Вы можете определить функцию
val fb: (B) -> Int = { it.onlyB() }
val fa: (A) -> Int = fb // ERROR
Функция fb
не сможет работать с классом A
, так как A
не имеет функции onlyB()
. Как следствие, вам не разрешено преобразовывать его в тип функции, который принимает A
параметры.
Эта концепция называется контравариантность, что означает, что входные параметры могут быть ограничены, только став более конкретно, а не более абстрактными. Итак, работает обратное направление:
val fa: (A) -> Int = { it.hashCode() }
val fb: (B) -> Int = fa // OK, every B is also an A
Напротив, для возвращаемых значений применяется концепция ковариация. Это означает, что возвращаемым значениям разрешено становиться более абстрактный, но не более конкретными:
val fInt: (B) -> Int = { it.onlyB() }
val fNum: (B) -> Number = fInt // OK, every Int is also a Number
Эти отношения можно использовать в универсальных классах, используя ключевые слова Kotlin in
(контравариантность) и out
(ковариантность) — подробное объяснение см. в здесь.
Отличный ответ, но есть ли способ избежать возможной путаницы между в разнобой и неизменность? (например, «в» дисперсии?)
Я думаю, что лучшими терминами (также известными на других языках) являются ковариация/контравариантность (см. Википедия). Котлин говорит о вход/выход в контексте дисперсия, но не явно «в дисперсии/вне дисперсии». Я могу отредактировать сообщение соответствующим образом.
Даже лучше! (Правда, теперь в последнем абзаце опечатка, которая сильно портит эффект… :)
И, конечно же, определенные способы использования предотвращают любой тип дисперсии.
Я пробовал это, но тогда это не позволяет мне пройти A. Подробности смотрите в обновлении.