第一个是正确的 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
的默认值0
Unit
的默认值是()
,并且由于只有此值可用,编译器接受它。
从文档:
如果 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}
,解释有点复杂和出乎意料:
new ABC {...}
相当于new ABC() {...}
new ABC()
可以解析为new ABC(())
,因为自动更新是规范中未提及的解析器功能,请参阅 SI-3583 规范未提及自动更新。相同的功能需要编译以下代码而不会出错:def f(a: Unit) = {} f() def g(a: (Int, Int)) = {} g(0,1)
请注意,调用会生成警告(即使是原始示例也会生成警告):
通过插入 () 来调整参数列表已被弃用:这不太可能是您想要的。
该警告是从 2.11 开始生成的,请参阅问题 SI-8035 弃用自动 () 插入。