问题描述
以下简单的 Kotlin 代码片段
fun main() {}
编译得很好,但以下内容
val main : () -> Unit = {}
让编译器抱怨“在项目中找不到主要方法。”,而我希望它们是等效的(我希望编程语言在概念上尽可能统一)。
为什么会这样?它仅与 main
相关,还是这种行为涉及更大的函数类?有subtle difference between declaring functions with "fun
" and declaring them as lambdas吗?
解决方法
从概念上讲,它们是不同的东西。为了看到这一点,让我们大致看一下等效的 Java 是什么。我将在此答案中使用 JVM 作为示例,但相同的原则适用于所有其他 Kotlin 后端。
object Foo {
fun main() { ... }
}
大概是这样
class Foo {
public static void main() { ... }
}
再次,粗略地说。从技术上讲,除非您使用 @JvmStatic
,否则您将获得一个单例对象和一个方法(我假设 main
有一些特殊处理会在 JVM 上生成静态函数,但我不知道事实)
另一方面,
object Foo {
val main: () -> Unit = { ... }
}
在这里,我们声明了一个属性,它在 Java 中将被实现为一个 getter-setter 对
class Foo {
// Singleton instance
public static Foo instance = new Foo();
public Supplier<Void> main;
Foo() {
main = new Supplier<Void>() {
Void get() {
...
}
}
}
}
也就是说,实际上没有 main
方法。有一个 main
字段,在它的深处,里面有一个函数。在我上面的示例中,该函数称为 get
。在 Kotlin 中,它被称为 invoke
。
我喜欢的想法是这样的。 Kotlin 中的方法(即您在指定其行为的对象上定义的东西)本身并不是一等对象。他们是二等公民,存在于一个物体上。您可以通过将它们转换为函数来将它们转换为一流的对象。函数是普通对象,就像任何其他对象一样。如果你使用一个普通对象,它可能是也可能不是一个函数,并用 ()
调用它,那么你实际上是在调用它的方法 .invoke(...)
。也就是说,()
是对象上的 operator,它实际上最终调用了一个方法。因此,在 Kotlin 中,函数实际上只是具有自定义 invoke
和大量语法糖的对象。
您的 val
定义了一个字段,它是一个函数。您的 fun
定义了一个方法。这两个都可以用()
调用,但只有一个是真正的方法调用;另一个正在秘密调用另一个对象上的 .invoke
。它们在语法上看起来相同这一事实无关紧要。
正如古老的格言所说,函数是穷人的对象,对象是穷人的函数。
,存在细微(或不止细微)的差异。用 val
声明它意味着 main
是一个包含对匿名函数(您用 lambda 定义)的引用的属性。如果用val
来定义,那么当你调用main()
时,实际上是在调用main
属性的getter,然后使用invoke()
运算符调用{{ 1}} 属性的返回值(匿名函数)。