В Scala; следует ли использовать черту App?

Я только начал изучать Scala, и многие из обучающих программ, которые я следую, используют комбинацию различных представлений для метода main. Помимо знакомого основного метода; там также используются черты App или Application. Похоже, что Application устарел и не рекомендуется, но я не могу найти никакой информации, которая объясняет многое помимо этого о каждом из этих способов определения точки входа.

Итак, мне интересно, может ли кто-нибудь объяснить мне:

  • Как работают черты App и Application?
  • Почему черта Application больше не рекомендуется и что делает черта App, которая отличается?
  • Где я должен использовать традиционный основной метод и когда я должен использовать App для запуска моей программы? Какая разница между этими двумя подходами?

Ответ 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!")
  }
}