Application trait 的问题实际上在其文档中有所描述:
(1) 引用对象的线程代码将阻塞,直到静态初始化完成。但是,由于扩展 Application 的对象的整个执行过程都是在静态初始化期间进行的,如果并发代码必须与封闭对象同步,则总是会死锁。
这是一个棘手的问题。如果你扩展 Application 特征,你基本上是在创建一个 Java 类:
class MyApplication implements Application {
static {
// All code goes in here
}
}
JVM 在MyApplication 类上隐式同步运行上述类初始化程序。这样,可以确保在初始化其类之前不会创建 MyApplication 的实例。如果您从应用程序中生成一个线程,该线程再次需要访问MyApplication 的实例,您的应用程序将死锁,因为类初始化仅在整个程序执行后才完成。这意味着一个悖论,因为只要您的程序正在运行,就无法创建任何实例。
(2) 如上所述,无法获取命令行参数,因为扩展 Application 的对象主体中的所有代码都作为静态初始化的一部分运行,该初始化发生在 Application 的 main 方法开始执行之前。
类初始化器不接受任何参数。此外,它首先运行,然后才能将任何值传递给类,因为在您甚至可以分配静态字段值之前需要执行类初始化程序。因此,您通常在 main 方法中收到的 args 将丢失。
(3) 静态初始化程序在程序执行期间只运行一次,JVM 作者通常假设它们的执行时间相对较短。因此,某些 JVM 配置可能会变得混乱,或者根本无法优化或 JIT 扩展应用程序的对象主体中的代码。这可能会导致性能显着下降。
JVM 会优化频繁运行的代码。通过这种方式,它确保不会将运行时间浪费在并非真正成为性能瓶颈的方法上。但是,它安全地假设 static 方法只执行一次,因为它们不能手动调用。因此,它不会优化从类初始化程序运行的代码,但是如果您使用 Application 特征,它是您的应用程序的 main 方法代码。
App trait 通过扩展 DelayedInit 解决了所有这些问题。 Scala 编译器明确知道此特征,因此初始化代码不是从类初始化程序而是从另一个方法运行的。请注意 for name 引用,它是 trait 的唯一方法:
trait Helper extends DelayedInit {
def delayedInit(body: => Unit) = {
println("dummy text, printed before initialization of C")
body
}
}
在实现DelayedInit 时,Scala 编译器会将其实现类或对象的任何初始化代码包装到一个 for name 函数中,然后将该函数传递给delayedInit 方法。没有直接执行初始化代码。这样,您还可以在初始化程序运行之前运行代码,例如,允许 Scala 将应用程序的运行时指标打印到控制台,该控制台围绕程序的入口点和出口包装。但是,有some caveats of this approach,因此不推荐使用DelayedInit。你真的应该只依赖App trait,它解决了Application trait 带来的问题。你不应该直接实现DelayedInit。
如果您愿意,您仍然可以定义 main 方法,只要您在 object 中定义它即可。这主要是风格问题:
object HelloWorld {
def main(args: Array[String]) {
println("Hello, world!")
}
}