Подтвердить что ты не робот

Определение функции: 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? Есть ли разница в производительности или семантике, которые мне не хватает?

4b9b3361

Ответ 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 аналогично, вы должны обернуть его в лямбду.