【问题标题】:Function definition: fun vs val函数定义:fun vs val
【发布时间】:2023-04-09 00:56:02
【问题描述】:

我很好奇在 Kotlin 中定义成员函数的建议方法是什么。考虑这两个成员函数:

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 为我明确定义 ::fg 的类型,它给了我这个:

val fref: KFunction1<Int, Int> = ::f

val gref: (Int) -> Int = g

所以一个是KFunction1&lt;Int, Int&gt; 类型,另一个是(Int) -&gt; Int 类型。很容易看出,两者都代表Int -&gt; Int类型的函数。

这两种类型有什么区别,在哪些情况下很重要?我注意到对于顶级函数,我可以使用任何一个定义来很好地组合它们,但是为了使上述组合能够编译,我必须这样写:

val z = g andThen A::f.partially1(this)

即我必须先将其部分应用到this

既然在函数中使用vals 时我不必经历这些麻烦,我是否有理由使用fun 定义非单元成员函数?是否存在我遗漏的性能或语义差异?

【问题讨论】:

  • 关于 vals 的一个重要问题是缺乏参数性,这无法编译:val id = fun&lt;T&gt;(t:T) = t
  • 啊,好点子!没想到。绝对值得考虑。
  • g 是否有权访问实例属性?
  • @JacobZimmerman 确实如此,尽管从我的示例中确实不明显。感谢您指出。

标签: kotlin


【解决方案1】:

Kotlin 是关于 Java 互操作性的,将函数定义为 val 将在互操作性方面产生完全不同的结果。以下 Kotlin 类:

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;
  }
}

如您所见,主要区别在于:

  1. fun f 只是一个常用的方法,而val g 实际上是一个返回另一个函数的高阶函数
  2. val g 涉及创建一个新类,如果您以 Android 为目标,这并不好
  3. val g 需要不必要的装箱和拆箱
  4. val g 不能轻易地从 java 中调用:A().g(42) 在 Kotlin 中与 new A().getG().invoke(42) 在 Java 中

更新:

关于A::f 语法。编译器将为 A::f 的出现生成一个额外的Function2&lt;A, Integer, Integer&gt; 类,因此以下代码会生成两个额外的类,每个类有7 个方法

val first = A::f
val second = A::f

Kotlin 编译器目前还不够聪明,无法优化此类事情。您可以在https://youtrack.jetbrains.com/issue/KT-9831 为该问题投票。如果您有兴趣,以下是每个类在字节码中的样子:https://gist.github.com/nsk-mironov/fc13f2075bfa05d8a3c3

【讨论】:

  • 啊,太好了,这正是我正在寻找的信息。后续问题:使用::f“具体化”f 是否会导致 Kotlin 创建一个内部函数类,类似于您在此处为g 所做的描述?
  • 基本上是的。只有当你使用inline函数时,才可能跳过类生成。
【解决方案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))
}

fg 分别是:

  fun f(x: Int) = 42

  val g = fun(x: Int) = 42

您可以看到 g 是一个可以像 lambda 一样使用的对象,但 f 不能。要类似地使用 f,您必须将其包装在 lambda 中。

【讨论】:

  • 您能否添加 fg 函数定义,以便我们在阅读您的答案时有更多上下文?
  • f 和 g 在问题中定义。
  • 是的,但是复制它们有助于提高可读性并节省一些鼠标滚轮,特别是因为问题和您的答案之间还有其他答案
  • 这不只是因为语法不同吗? a.g 是一个有趣的参考; a.f 不是;您应该使用方法参考语法:a::f。刚刚对此进行了测试,exe(a::f) 确实有效!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-01
  • 2013-11-05
  • 1970-01-01
  • 2017-09-19
  • 2014-11-10
  • 2013-09-24
相关资源
最近更新 更多