Ответ 1
Проблема с признаком Application
описана в его документации:
(1) Резьбовой код, который ссылается на объект, будет блокироваться до завершения статической инициализации. Однако, поскольку полное выполнение объекта, расширяющего приложение, происходит во время статической инициализации, параллельный код всегда будет тупиковым, если он должен синхронизироваться с окружающим объектом.
Это сложно. Если вы расширите черту Application
, вы в основном создаете класс Java:
class MyApplication implements Application {
static {
// All code goes in here
}
}
JVM запускает указанный выше инициализатор класса, неявно синхронизированный в классе MyApplication
. Таким образом, уверен, что экземпляр MyApplication
не создается до инициализации его класса. Если вы создаете поток из вашего приложения, которому еще нужно получить доступ к экземпляру MyApplication
, ваше приложение будет заблокировано, поскольку инициализация класса будет завершена только после выполнения всей программы. Это подразумевает парадокс, поскольку ни один экземпляр не может быть создан до тех пор, пока ваша программа работает.
(2) Как описано выше, нет способа получить аргументы командной строки, потому что весь код в теле объекта, расширяющего приложение, запускается как часть статической инициализации, которая возникает до того, как основной метод приложения даже начинает выполнение.
Инициализатор класса не принимает никаких аргументов. Кроме того, он запускается первым, прежде чем любые значения могут быть переданы классу, поскольку инициализатор класса должен быть выполнен, прежде чем вы сможете даже присвоить значение статического поля. Таким образом, args
, который вы обычно получаете по методу main
, теряется.
(3) Статические инициализаторы запускаются только один раз во время выполнения программы, а авторы JVM обычно предполагают, что их выполнение является относительно коротким. Поэтому некоторые конфигурации JVM могут запутаться или просто не могут оптимизировать или JIT код в теле объекта, расширяющего приложение. Это может привести к значительному снижению производительности.
JVM оптимизирует код, который выполняется часто. Таким образом, он гарантирует, что время работы не будет потрачено впустую на методы, которые на самом деле не являются горлышком бутылки производительности. Тем не менее, он надежно полагает, что методы static
выполняются только один раз, поскольку они не могут быть вызваны вручную. Таким образом, он не будет оптимизировать код, который запускается из инициализатора класса, который, однако, является вашим приложением main
, если вы используете черту Application
.
Функция App
устраняет все это, расширяя DelayedInit
. Эта черта явно известна компилятору Scala, так что код инициализации не запускается из инициализатора класса, а из другого метода. Обратите внимание на ссылку на имя, которая используется только для метода trait:
trait Helper extends DelayedInit {
def delayedInit(body: => Unit) = {
println("dummy text, printed before initialization of C")
body
}
}
При реализации DelayedInit
компилятор Scala обертывает любой код инициализации своего реализующего класса или объекта в функцию имени, которая затем передается методу DelayedInit
. Никакой код инициализации не выполняется напрямую. Таким образом, вы также можете запускать код до запуска инициализатора, что позволяет Scala, например, распечатывать метрики выполнения приложений на консоли, которая обернута вокруг точки входа и выхода программы. Однако существуют некоторые предостережения этого подхода, поэтому использование DelayedInit
считается устаревшим. Вы действительно должны полагаться только на черту App
, которая решает проблемы, налагаемые чертой Application
. Вы не должны использовать DelayedInit
напрямую.
Вы все равно можете определить метод main
, если хотите, до тех пор, пока вы определяете его в object
. Это в основном вопрос стиля:
object HelloWorld {
def main(args: Array[String]) {
println("Hello, world!")
}
}