Scala App的特点和主要内部工作原理如何?

Hac*_*g J 0 scala

嗨,我是斯卡拉的新手.

据我所知,在scala中有2路进入入口点,一个是使用object定义main方法,另一个是扩展App trait.

我想知道App trait是如何工作的,所以我检查了App trait 的来源,但是有很多令人困惑的代码......

该代码表示​​App具有从App trait 扩展的initCodes,并且这些initCit是从DelayedInit继承的delayedInit方法中添加的.另外,应用程序特点主要有方法,这将是切入点.

但令我困惑的是

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

dad*_*ade 6

  1. 谁打电话给delayedInit?是在调用main方法之前调用的吗?(我猜是的)

delayedInit将自动地由Scala编译器为对象/类,它扩展了的初始化代码调用DelayedInit性状.我在下面的答案中进一步扩展.

  1. 为什么initCodes是ListBuffer而不是元素?我认为应用程序中只有一个入口点,所以我认为它不应该是复数.

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

  1. 我在哪里可以查看这些知识?我试图在文档中搜索,但我不能.

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

我现在尝试通过详细介绍其工作原理DelayedInit以及JVM如何指定程序的入口点来详细阐述上述答案.

首先,我们必须明白,当Scala在JVM上运行时,它仍然必须遵守JVM要求来定义程序的入口点,即为JVM提供带有签名的main方法的类.的public static void main(String[]).即使我们使用App特征时,看起来好像我们正在远离这样做,但这只是一种幻觉,JVM仍然需要访问带签名的方法public static void main(String[]).只是通过App与机制一起扩展DelayedInit,Scala可以代表我们提供这种方法.

其次,重申在类(或对象)定义的主体中找到的代码片段将是这样的类/对象的初始化代码并且在实例化时将自动执行也是很好的.在Java中,它或多或少是您在构造函数块中放置的代码.

所以对于一个班级:

class Foo {
// code.
def method = ???
}
Run Code Online (Sandbox Code Playgroud)

无论如何code,它会在你打电话时自动执行new Foo.

如果是对象

object Foo {
// code.
def method = ???
}
Run Code Online (Sandbox Code Playgroud)

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

所以基本上如果身体定义中有任何东西,它会自动执行.您不需要显式执行它.

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

特技DelayedInit允许我们执行的特性是获取作为类/对象定义主体一部分的代码,并将其转换为通过名称传递给自delayedInit定义的方法的参数DelayedInit.

基本上它重写了这个:

object Foo {
// some code
} 
Run Code Online (Sandbox Code Playgroud)

object Foo {
// delayedInt({some code})
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在让我们看看它如何与App特征结合,以及如何使用App特征为Scala应用程序提供入口点.

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

App特点是这样的实现.这个App特质有什么作用?与讨论主题相关的两件重要事情:

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

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

基本上它变成了这样:

object Foo {
// some code
}
Run Code Online (Sandbox Code Playgroud)

进入这个

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 
??? 
  }
}
Run Code Online (Sandbox Code Playgroud)

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

给出以下代码段:

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")
}
Run Code Online (Sandbox Code Playgroud)

运行时输出以下内容:

Initialising AnotherClass
Initialising AnotherTrait
Initialising YetAnotherTrait
Hello world
Run Code Online (Sandbox Code Playgroud)

因此,在一个由层次结构中的各个初始化代码 AnotherClass,AnotherTrait以及YetAnotherTrait被添加到initCode列表缓冲区,通过delayedInit该方法App特点,然后他们得到的也由提供的主要方法执行的App特质.

正如您在窥视源代码时所注意到的那样,整个机制已DelayedInt被弃用,并计划在将来删除.