【问题标题】:In Scala; should I use the App trait?在斯卡拉;我应该使用 App 特征吗?
【发布时间】:2014-08-17 17:07:45
【问题描述】:

我刚刚开始学习 Scala,我学习的许多教程都使用了 main 方法的不同表示形式的组合。除了熟悉的 main 方法;还可以使用特征 AppApplicationApplication 似乎已被弃用且不推荐使用,但我找不到任何信息可以解释更多关于定义入口点的这些方法的更多信息。

所以,我想知道是否有人可以向我解释:

  • 特征AppApplication 是如何工作的?
  • 为什么不再推荐 Application 特征,App 特征有什么不同?
  • 应该在哪里使用传统的 main 方法,什么时候应该使用App 来启动我的程序?这两种方法有什么区别?

【问题讨论】:

  • 我看过那个 SO 线程,但是关于 Application 的帖子是新的和有趣的,谢谢。我认为App 继承了与Application 相同的警告,因为它也实现了构造函数?那么,它们之间有什么区别呢?
  • 请注意,Application trait 在当前版本的 Scala 2.11 中不再存在。
  • 对于 Apache Spark 作业,文档指出“应用程序应该定义一个 main() 方法而不是扩展 scala.App。scala.App 的子类可能无法正常工作。”

标签: scala main traits


【解决方案1】:

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!")
  }
}

【讨论】:

  • 你在说main方法有什么问题?在什么情况下应该使用App 而不是main
  • 定义main方法与定义Java main方法相比没有问题,因为它是在初始化类后由Java虚拟机调用的。 App 特征是一条捷径。除了类初始化器,main 方法可以优化,因为它的代码可以进行堆栈替换。
  • @mickliddy 最后一句话是无意的:我的意思是Application,而不是main
  • 啊,现在说得通了。由于delayedInit,使用App 还允许以与main 相同的方式优化代码,不是吗?
猜你喜欢
  • 2017-05-13
  • 2017-10-23
  • 2018-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-12
相关资源
最近更新 更多