Определение функции: fun vs val
Мне интересно, что такое предложенный способ определения функций-членов в Котлине. Рассмотрим эти две функции-члена:
class A {
fun f(x: Int) = 42
val g = fun(x: Int) = 42
}
Они, похоже, совершают одно и то же, но я обнаружил тонкие различия.
Определение на основе val
, например, представляется более гибким в некоторых сценариях. То есть, я не мог выработать прямой способ составить f
с другими функциями, но я мог бы с g
. Чтобы играть с этими определениями, я использовал библиотеку funKTionale. Я обнаружил, что это не скомпилируется:
val z = g andThen A::f // f is a member function
Но если f
были определены как val
, указывающие на одну и ту же функцию, он будет компилироваться просто отлично. Чтобы выяснить, что происходит, я попросил IntelliJ явно определить тип ::f
и g
для меня, и он дает мне следующее:
val fref: KFunction1<Int, Int> = ::f
val gref: (Int) -> Int = g
Итак, один имеет тип KFunction1<Int, Int>
, другой - типа (Int) -> Int
. Легко видеть, что обе представляют собой функции типа Int -> Int
.
В чем разница между этими двумя типами, и в каких случаях это имеет значение? Я заметил, что для функций верхнего уровня я могу скомпоновать их с помощью любого определения, но для того, чтобы сделать вышеупомянутый состав компиляцией, мне пришлось написать его так:
val z = g andThen A::f.partially1(this)
то есть. Я должен был сначала применить его к this
.
Так как мне не нужно проходить эту проблему при использовании val
для функций, есть ли причина, почему я должен когда-либо определять функции-члены не-Unit с помощью fun
? Есть ли разница в производительности или семантике, которые мне не хватает?
Ответы
Ответ 1
Kotlin - это все о совместимости Java, и определение функции как val
приведет к совершенно другому результату с точки зрения взаимодействия. Следующий класс Котлина:
class A {
fun f(x: Int) = 42
val g = fun(x: Int) = 42
}
эффективно эквивалентно:
public class A {
private final Function1<Integer, Integer> gref = new Function1<Integer, Integer>() {
@Override
public Integer invoke(final Integer integer) {
return 42;
}
};
public int f(final int value) {
return 42;
}
public Function1<Integer, Integer> getG() {
return gref;
}
}
Как вы можете видеть, основные отличия:
-
fun f
является обычным методом, а val g
фактически является функцией более высокого порядка, которая возвращает другую функцию
-
val g
подразумевает создание нового класса, который не очень хорош, если вы настроите Android на
-
val g
требует ненужного бокса и распаковки
-
val g
не может быть легко вызван из java: A().g(42)
в Kotlin vs new A().getG().invoke(42)
в Java
UPDATE:
Относительно синтаксиса A::f
. Компилятор будет генерировать дополнительный класс Function2<A, Integer, Integer>
для каждого A::f
, поэтому следующий код приводит к двум дополнительным классам с 7 методами:
val first = A::f
val second = A::f
Компилятор Kotlin на данный момент недостаточно умен, чтобы оптимизировать такие вещи. Вы можете проголосовать за проблему здесь https://youtrack.jetbrains.com/issue/KT-9831. Если вам интересно, вот как каждый класс смотрит в байт-код: https://gist.github.com/nsk-mironov/fc13f2075bfa05d8a3c3
Ответ 2
Вот некоторый код, показывающий, как f и g отличаются, когда дело доходит до использования:
fun main(args: Array<String>) {
val a = A()
exe(a.g) // OK
//exe(a.f) // does not compile
exe { a.f(it) } // OK
}
fun exe(p: (Int) -> Int) {
println(p(0))
}
Где f
и g
находятся:
fun f(x: Int) = 42
val g = fun(x: Int) = 42
Вы можете видеть, что g - это объект, который может использоваться как лямбда, но f не может. Чтобы использовать f аналогично, вы должны обернуть его в лямбду.