Scala 编译器在使用惰性参数初始化类时的奇怪行为



第一个是正确的 Scala 代码,但第二个甚至无法编译的可能性有多大?

编译的那个

object First {
class ABC(body: => Unit) {
val a = 1
val b = 2
println(body)
}
def main(args: Array[String]): Unit = {
val x = new ABC {
a + b
}
}
}

这个不能在 Scala 2.11 和 2.12 上编译

object Second {
class ABC(body: => Int) {
val a = 1
val b = 2
println(body)
}
def main(args: Array[String]): Unit = {
val x = new ABC {
a + b
}
}
}

> 一点也不奇怪。让我们看第一个示例:

您声明您的类ABC接收返回Unit的 pass by name 参数,并且您认为以下代码段:

val x = new ABC {
a + b
}

正在传递该body参数,它不是。真正发生的事情是:

val x = new ABC(()) { a + b }

如果您运行该代码,您将看到println(body)打印 (),因为您没有为body参数传递值,编译器允许它编译,因为正如 scaladoc 所述,只有 1 个值类型为Unit

单位是斯卡拉的一个子类型。任瓦尔。只有一个 Unit () 类型的值,并且它不由基础运行时系统中的任何对象表示。返回类型为 Unit 的方法类似于声明为 void 的 Java 方法。

由于只有一个值,编译器允许您省略它,它将填补空白。单例对象不会发生这种情况,因为它们不会扩展AnyVal。只有Int的默认值0Unit的默认值是(),并且由于只有此值可用,编译器接受它。

从文档:

如果 ee 具有某种值类型并且预期类型为 Unit,则通过将 ee 嵌入术语 { ee;() }.

单一实例对象不会扩展AnyVal因此它们不会得到相同的处理。

当您使用如下语法时:

new ABC {
// Here comes code that gets executed after the constructor code. 
// Code here can returns Unit by default because a constructor always 
// returns the type it is constructing. 
}

你只是在向构造函数体添加东西,而不是传递参数。

第二个示例无法编译,因为编译器无法推断出body: => Int的默认值,因此您必须显式传递它。

结论

括号内的代码与传递参数不同。在相同的情况下,它可能看起来相同,但这是由于"魔术"。

不能将单个参数传递给大括号中的构造函数,因为这将被解析为定义匿名类。如果要执行此操作,还需要将大括号括在普通大括号中,如下所示:

new ABC({
a + b
})

至于编译器为什么接受new ABC {a + b},解释有点复杂和出乎意料:

  1. new ABC {...}相当于new ABC() {...}
  2. new ABC()可以解析为new ABC(()),因为自动更新是规范中未提及的解析器功能,请参阅 SI-3583 规范未提及自动更新。相同的功能需要编译以下代码而不会出错:

    def f(a: Unit) = {}
    f()
    def g(a: (Int, Int)) = {}
    g(0,1)
    

    请注意,调用会生成警告(即使是原始示例也会生成警告):

    通过插入 () 来调整参数列表已被弃用:这不太可能是您想要的。

    该警告是从 2.11 开始生成的,请参阅问题 SI-8035 弃用自动 () 插入。

相关内容

  • 没有找到相关文章

最新更新