Scala对象初始化

时间:2014-06-14 15:45:41

标签: scala

我有一个对象:

object A {
 val init = println("Hello")
}

我在特质中使用它:

trait SomeTratit {
  val a = A.init
}

然后我在课堂上使用trait:

class SomeClass extends SomeTrait

当我使用new SomeClass实例化SomeClass时,我希望在控制台中看到"Hello",但是没有得到它。为什么呢?

我希望看到"你好"实例化几个对象时只有一次,但是没有看到任何" Hello"在控制台

5 个答案:

答案 0 :(得分:4)

如果你的代码中确实需要这样的副作用 - 你可以在直接访问A时看到“你好”(所以只需在你的特性中提到A)。这是对象的一个​​特性 - 它们不需要初始化,而不需要:

scala> object A {val a = println("hello"); val b = (); println("bye") }
defined module A

scala> A.b

scala> A.a

scala> A
hello
bye
res10: A.type = A$@1be2f5d8

您可能会注意到类型成员的类似行为:

scala> object A {println("init"); type T = Int }
defined module A

scala> val i: A.T = 5
i: A.T = 5

scala> A
init
res15: A.type = A$@73f5477e

scala>

答案 1 :(得分:4)

我不知道这是否应该被视为一个错误,但现在发生了这种情况。

如果你看一下object A生成的字节码,就像这样:

public final class A$ {
  public static final A$ MODULE$;

  private final scala.runtime.BoxedUnit init;

  public static {};
    Code:
       0: new           #2                  // class A$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  public void init();
    Code:
       0: return

  private A$();
    Code:
       0: aload_0
       1: invokespecial #16                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #18                 // Field MODULE$:LA$;
       8: aload_0
       9: getstatic     #23                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
      12: ldc           #25                 // String Hello
      14: invokevirtual #29                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
      17: getstatic     #34                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
      20: putfield      #36                 // Field init:Lscala/runtime/BoxedUnit;
      23: return
}

您可以在println("Hello")的构造函数中找到您期望的A$,但init()中没有任何内容。这是完全正确的,因为您打算每次拨打println("Hello")时都不会执行init(),对吗?

然而,问题出在SomeClass

public class SomeClass implements SomeTrait {
  public void a();
    Code:
       0: return

  public void SomeTrait$_setter_$a_$eq(scala.runtime.BoxedUnit);
    Code:
       0: return

  public SomeClass();
    Code:
       0: aload_0
       1: invokespecial #21                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: invokestatic  #27                 // Method SomeTrait$class.$init$:(LSomeTrait;)V
       8: return
}

什么? SomeClass.a()中也没有任何内容!但是考虑一下,它也是完全合理的:为什么我会打扰它,因为A$.init()中几乎什么也没有,它什么都没有返回(即没有要设置的字段)?为什么不优化它(也许Java做了这个优化。或者Scala做了。我不知道)?但是,此优化也会删除A$的唯一外观,这意味着不会为A$调用构造函数。这就是Hello永远不会出现的原因。

但是,如果你稍微改变一下代码,那么init()的字节码就不会是空的,如下所示:

object A {
  val init = { println("Hello"); 1 }
}

编译为以下字节码:

public int init();
  Code:
     0: aload_0
     1: getfield      #17                 // Field init:I
     4: ireturn

在这种情况下,您会找到SomeClass.a()的字节码,如下所示:

public int a();
  Code:
     0: aload_0
     1: getfield      #15                 // Field a:I
     4: ireturn

字段在SomeTrait$class中设置:

public abstract class SomeTrait$class {
  public static void $init$(SomeTrait);
    Code:
       0: aload_0
       1: getstatic     #13                 // Field A$.MODULE$:LA$;
       4: invokevirtual #17                 // Method A$.init:()I
       7: invokeinterface #23,  2           // InterfaceMethod SomeTrait.SomeTrait$_setter_$a_$eq:(I)V
       12: return
}
调用

A$.init()来设置此字段,因此在这种情况下,您可以预期显示Hello

答案 2 :(得分:1)

在我看来,它与编译器优化有关。例如,给定的代码不显示"Hello"消息,因为编译器似乎推断出A.init类型为Unit,本地val a SomeTrait总是会以相同的常数值结束。所以编译器只需分配它就可以了。

尽管如此,如果您对init的评价并不急切,只需在init Object A处设置字段lazy,就会显示该消息:

object A {
  lazy val init = println("Hello")
}

此外,即使您只是执行相同操作但强制init中的Object A函数返回一些有意义的值,也会打印该消息,因为编译器无法将结果值推断为保持不变。

  object A {
    val init = { println("Hello"); 1} // returns some integer
  }

  trait SomeTrait {
    val a = A.init
  }

  class SomeClass extends SomeTrait

这是我的解释,但我可能会遗漏一些东西。希望它有所帮助。

答案 3 :(得分:0)

当您在特征中初始化a时,您所做的就是将函数A.init的引用复制到SomeTrait.a中。然后,您可以执行a()来调用该函数并查看预期的输出。

答案 4 :(得分:-4)

init是类型Unit,因此评估函数是徒劳的。