The problem with the Application
trait is actually described in its documentation:
(1) Threaded code that references the object will block until static initialization is complete. However, because the entire execution of an object extending Application takes place during static initialization, concurrent code will always deadlock if it must synchronize with the enclosing object.
This is a tricky one. If you extend the Application
trait, you are basically creating a Java class:
class MyApplication implements Application {
static {
// All code goes in here
}
}
The JVM runs the above class initializer implicitly synchronized on the MyApplication
class. This way, it is assured that no instance of MyApplication
is created before its class is initialized. If you spawn a thread from your application that again needs to access an instance of MyApplication
, your application would dead lock as the class initialization is only complete after the entire program has executed. This implies a paradox as no instance can be created as long as your program is running.
(2) As described above, there is no way to obtain the command-line arguments because all code in body of an object extending Application is run as part of the static initialization which occurs before Application's main method even begins execution.
A class initializer does not take any arguments. Also, it is run first, before any values could be handed to the class as the class initializer needs to be executed before you could even assign a static field value. Thus, the args
that you normally receive on a main
method are lost.
(3) Static initializers are run only once during program execution, and JVM authors usually assume their execution to be relatively short. Therefore, certain JVM configurations may become confused, or simply fail to optimize or JIT the code in the body of an object extending Application. This can lead to a significant performance degradation.
The JVM optimizes code that is run frequently. This way, it makes sure that no run time is wasted on methods that are not really a performance bottle neck. However, it safely assumes that static
methods are only executed once as they cannot be invoked manually. Thus, it will not optimize the code that is run from a class initializer which is however your application's main
method code if you are using the Application
trait.
The App
trait fixes all this by extending DelayedInit
. This trait is explicitly known to the Scala compiler such that initialization code is not run from a class initializer but from another method. Note the for name reference that is haded to the trait's only method:
trait Helper extends DelayedInit {
def delayedInit(body: => Unit) = {
println("dummy text, printed before initialization of C")
body
}
}
When implementing DelayedInit
, the Scala compiler wraps any initialization code of its implementing class or object into a for name function which is then passed to the delayedInit
method. No initialization code is executed directly. This way, you can also run code before an initializer is run what allows Scala for example to print an application's runtime metrics to the console which is wrapped around the program's entry point and exit. However, there are some caveats of this approach and using DelayedInit
is therefore deprecated. You should really only rely on the App
trait which solves the problems that are imposed by the Application
trait. You should not implement DelayedInit
directly.
You can still define a main
method if you want to, as long as you define it in an object
. This is mostly a matter of style:
object HelloWorld {
def main(args: Array[String]) {
println("Hello, world!")
}
}