【问题标题】:How Scala App trait and main works internally?Scala App trait 和 main 如何在内部工作?
【发布时间】:2018-11-25 14:12:06
【问题描述】:

您好,我是 Scala 的新手。

据我所知,在 scala 中创建入口点有两种方法,一种是使用对象定义 main 方法,另一种是扩展 App 特征。

我想知道 App trait 是如何工作的,所以我检查了 App trait 的来源,但是有充满了混乱的代码...

代码表示应用有 initCodes 扩展自 App trait,这些是添加的在继承自 DelayedInit 的 delayedInit 方法中。 App trait 也有 main 方法,这将是入口点。

但让我困惑的是

  1. 谁调用 delayedInit?是在调用main方法之前调用吗?(我猜是)
  2. 为什么 initCodes 不是 ListBuffer 元素?我认为应用程序只有一个入口点,所以我认为它不应该是复数。
  3. 在哪里可以查看这些知识?我试图在文档中搜索,但我不能

【问题讨论】:

    标签: scala


    【解决方案1】:
    1. 谁调用了delayedInit?是在调用main方法之前调用吗?(我猜是)

    delayedInit 将由 Scala 编译器自动调用,作为扩展 DelayedInit 特征的对象/类的初始化代码。我在下面详细介绍了这个答案。

    1. 为什么 initCodes 不是 ListBuffer 元素?我觉得申请只有一个切入点,所以我觉得不应该是复数。

    因为可以有一个类的层次结构,其中层次结构中每个类的初始化代码作为执行程序的一部分被执行。下面还提供了一个示例。

    1. 在哪里可以查看这些知识?我尝试在文档中搜索,但无法搜索。

    我通过阅读 Scala 文档及其指向的链接来了解动态。例如这个https://github.com/scala/scala/releases/tag/v2.11.0 和https://issues.scala-lang.org/browse/SI-4330?jql=labels%20%3D%20delayedinit%20AND%20resolution%20%3D%20unresolved

    我现在将尝试通过更详细地了解DelayedInit 的工作原理以及 JVM 如何指定程序的入口点来详细说明上述答案。

    首先我们要明白,当Scala运行在JVM上时,它还是要遵守JVM定义你程序的入口点的要求,即为JVM提供一个带有main的类签名为public static void main(String[]) 的方法。即使当我们使用App trait 时,看起来我们似乎已经摆脱了这样做,但这只是一种错觉,JVM 仍然需要访问带有签名public static void main(String[]) 的方法。只是通过扩展App和DelayedInit的机制,Scala可以为我们提供这个方法。

    其次,重申一下,在类(或对象)定义的主体中找到的代码 sn-ps 将是此类/对象的初始化代码,并且在实例化时会自动执行。在 Java 中,它或多或少是您放在构造函数块中的代码。

    所以对于一个类:

    class Foo {
    // code.
    def method = ???
    }
    

    无论code是什么,当你调用new Foo时它会自动执行。

    如果是对象

    object Foo {
    // code.
    def method = ???
    }
    

    code 将自动执行,您无需调用 new,因为 Scala 会自动为您提供一个名为 Foo 的单例实例。

    所以基本上,如果正文定义中有任何内容,它会自动执行。您不需要显式执行它。

    现在到 DelayedInit 特征。需要注意的一件事是,它为我们提供了一种机制来执行所谓的编译器技巧,其中我们的代码的某些部分被重写。这就是为什么它可能会令人困惑的原因之一。因为当你使用它时,Scala 编译器实际执行的不是你阅读的代码,而是对它的轻微修改。要了解发生了什么,您需要了解编译器更改代码的方式。

    DelayedInit trait 允许我们执行的技巧是获取作为类/对象定义主体的一部分的代码,并将其转换为按名称传递的参数,传递给方法 @987654340 @ 在DelayedInit 上定义。

    基本上它重写了这个:

    object Foo {
    // some code
    } 
    

    进入

    object Foo {
    // delayedInt({some code})
    }
    

    这意味着delayedInt 不是自动执行delayedInt,而是自动调用的方法,// some code 作为参数传递给它。

    因此,任何扩展 DelayedInit 的东西都会将其初始化代码替换为方法调用 delayedInt,并将初始化代码作为参数传递。因此,为什么没有人需要显式调用 delayedInt 方法。

    现在让我们看看它是如何与 App 特征相关联的,以及 App 特征如何用于提供 Scala 应用程序的入口点。

    您会注意到,DelayedInit 特征上的 delayedInit 方法不提供任何实现。因此,delayedInit 在被调用时的实际行为需要由扩展DelayedInit 的其他东西提供。

    App trait 就是这样一个实现。 App 特征有什么作用?与讨论主题相关的两个重要事项:

    1. 它提供了delayedInit 的实现,它接受它传递的初始化代码,并将其放入ListBuffer。
    2. 它提供了main方法def main(args: Array[String]),满足JVM的要求,有一个带有public static void main(String[])的方法作为程序的入口点。而这个 main 方法的作用是执行放在 ListBuffer 中的任何代码。

    App trait 的上述特征意味着任何扩展它的对象/类都会将其初始化代码传递给delayedInit,然后将其添加到 ListBuffer 中,然后扩展它的对象/类将现在有一个 main 方法,当调用它时(大部分时间由 JVM 作为入口点)将运行 ListBuffer 中的代码并执行它。

    基本上是这样的:

    object Foo {
    // some code
    }
    

    进入这个

    object Foo {
    
    // the implementation of delayedInt is to put `// some code` into a list buffer
    delayedInt (// some code)
    
    def main(args: Array[String]) = {
    // the implementation below just runs through and execute the code found in list buffer that would have been populated by the call to delayedInt and 
    ??? 
      }
    }
    

    那么为什么要有一个 List 缓冲区来存储要执行的代码呢?因为,正如我上面所说,可以有一个层次结构的类,层次结构中每个类的初始化代码作为执行程序的一部分被执行。看到这个在行动。

    给定以下代码sn-p:

    class AnotherClass {
      println("Initialising AnotherClass")
    }
    trait AnotherTrait {
      println("Initialising AnotherTrait")
    }
    trait YetAnotherTrait {
      println("Initialising YetAnotherTrait")
    } 
    
    object Runner extends AnotherClass with AnotherTrait with YetAnotherTrait with App {
      println("Hello world")
    }
    

    运行时会输出以下内容:

    Initialising AnotherClass
    Initialising AnotherTrait
    Initialising YetAnotherTrait
    Hello world
    

    因此,由AnotherClass、AnotherTrait 和YetAnotherTrait 组成的层次结构中的单独初始化代码通过App 特征的delayedInit 方法添加到initCode 列表缓冲区,然后它们由App trait 提供的 main 方法执行。

    正如您通过查看源代码会注意到的那样,DelayedInt 的整个机制已被弃用,并计划在未来移除。

    【讨论】:

    • 完美答案!我很感激
    【解决方案2】:

    延迟初始化:-

    初始化钩子。这将保存所有初始化代码以在其中执行 main。此方法通常不会直接从用户代码中调用。 相反,它被称为这些类的编译器生成的代码,并且 从 DelayedInit 特征继承的对象(但不是特征)和 自己没有定义delayedInit 方法。

    App scala

    delayedInit 自2.11.0 以来已被弃用。此外,它还有一些未修复的出色bugs。

    initCodes:-

    我不确定将initCodes 定义为ListBuffer 的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-11
      • 2017-05-31
      • 1970-01-01
      • 2019-12-28
      • 2021-09-21
      • 2013-09-26
      • 1970-01-01
      相关资源
      最近更新 更多