为什么我的主对象调用了另一个对象的所有方法,而实际上只调用了一个方法?

why does my main object, invoke all methods of another object, when only one is actually invoked?

学习 scala 的第 1 天:
这是我的代码,但在一种情况下我得到了意想不到的结果。

object Hello extends App {
  def square(x: Int) = x * x
  println("Hello, World!")
}

object main extends App {
  println(Hello.square(2))
}

那么输出是(正确的)

4

但是当我 运行 main 没有 Hello 扩展 App 时,

object Hello {
  def square(x: Int) = x * x
  println("Hello, World!")
}

object main extends App {
  println(Hello.square(2))
}

我明白了。

Hello, World!
4

这是怎么回事?当我简单地调用 Hello.square 函数时,为什么主要执行 Hello 对象中的所有内容?

我的理解是,当您引用对象时,对象块中的所有内容都会被评估。并且

object Hello {
  def square(x: Int) = x * x
  println("Hello, World!")
}

表现得像

object Hello {
  def square(x: Int) = x * x
  val _ = println("Hello, World!")
}

因为println()会产生副作用,而returnsUnit/(),在这种情况下会被立即丢弃。所以打印只是评估对象时产生的副作用。另请注意以下示例:

object Hello {
  def square(x: Int) = x * x
  println("Hello, World!")
}

object main extends App {
  println("first")
  Hello
  println("second")
  println(Hello.square(2))
  println("third")
}

这会打印

first
Hello, World!
second
4
third

所以 hello world 的打印只发生一次,它在引用 Hello 时发生,而不是在调用 square() 时发生。

通常,您不希望代码在对象中产生副作用。这更像是函数和 values/variables 的地方。例如:

object Hello {
  val w = "World"
  def greet(what: String): Unit = println(s"Hello, $what!")
}

object main extends App {
  Hello.greet(Hello.w)
  Hello.greet("Scala")
}

或者如果您希望值是动态的,您可以使用 class 代替:

class Hello(val w: String) {
  def greet(what: String): Unit = println(s"Hello, $what!")
}

object main extends App {
  val h = Hello("World")
  h.greet(h.w)
  h.greet("Scala")
}

编辑:更改了初始答案的一些错误细节。

使用 Object 本质上是使用静态 class,因此在构造对象时会评估其中的所有代码。所以,实际上预期的结果是 println 发生,不管你是否在对象上调用某些东西。

那么问题是,你怎么看不到这种行为

object Hello extends App {
  def square(x: Int) = x * x
  println("Hello, World!")
}

好吧,唯一的区别是 extends App 确实在文档中我们可以看到

Caveats

It should be noted that this trait is implemented using the DelayedInit functionality, which means that fields of the object will not have been initialized before the main method has been executed.

因此,当您的第一个实现建立在 App 实现的限制之上时 - 这可能会改变为

Future versions of this trait will no longer extend DelayedInit.

最好将任何“免费”代码放在 Hello 内部函数中,以防止您看到意想不到的副作用