我有一个对象:
object A {
val init = println("Hello")
}
我在特质中使用它:
trait SomeTratit {
val a = A.init
}
然后我在课堂上使用trait:
class SomeClass extends SomeTrait
当我使用new SomeClass
实例化SomeClass时,我希望在控制台中看到"Hello"
,但是没有得到它。为什么呢?
我希望看到"你好"实例化几个对象时只有一次,但是没有看到任何" Hello"在控制台
答案 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,因此评估函数是徒劳的。