Reputation: 6006
I have an object:
object A {
val init = println("Hello")
}
I use it in a trait:
trait SomeTratit {
val a = A.init
}
Then I use trait it in a class:
class SomeClass extends SomeTrait
When I instantiate SomeClass with new SomeClass
I expect to see "Hello"
in the console, but don't get it. Why?
Also I expect to see "Hello" only once while instantiating several objects, but don't see any "Hello" in console
Upvotes: 15
Views: 16110
Reputation: 910
I don't know if this should be considered a bug, but here's HOW this happened.
If you look at the generated bytecode of object A
, it's like this:
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
}
You can find the println("Hello")
you expected in the constructor of A$
, but there's nothing in init()
. This is perfectly correct because it is you intention that println("Hello")
would not be executed every time when you called init()
, right?
However, the problem comes in 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
}
What?! There's nothing in SomeClass.a()
, either! But think about it, it's also perfectly reasonable: why would I bother calling it since there's literally nothing in A$.init()
and it returns nothing (i.e. no field to be set)? Why not just optimize that out (Maybe Java did this optimization. Or Scala did. I don't know)? However, this optimization also erase the only appearance of A$
, which means no constructor will be called for A$
. That's why the Hello
never appeared.
However, if you change the code a little bit so that the bytecode of init()
will not be empty, like this:
object A {
val init = { println("Hello"); 1 }
}
which compiles to the following bytecode:
public int init();
Code:
0: aload_0
1: getfield #17 // Field init:I
4: ireturn
in which case you will find the bytecode for SomeClass.a()
like this:
public int a();
Code:
0: aload_0
1: getfield #15 // Field a:I
4: ireturn
where the field was set in 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()
is called to set this field, so in this case you can expect Hello
to appear.
Upvotes: 6
Reputation: 6242
Seems to me like it's related with the compiler optimization. For example, the code as given doesn't show the "Hello"
message, because the compiler seems to infer that as A.init
is of type Unit
, the local val a
of SomeTrait
will always end up with the same constant value. So the compiler just assign it and that's it.
Nonetheless, if you make the evaluation of init
not eager, by making the field init
at Object A
also lazy
, then the message is actually printed:
object A {
lazy val init = println("Hello")
}
Moreover, even if you just do the same but force the init
function in Object A
to return some meaningful value, then the message will also be printed, since the compiler cannot infer the resulting value to be constant.
object A {
val init = { println("Hello"); 1} // returns some integer
}
trait SomeTrait {
val a = A.init
}
class SomeClass extends SomeTrait
This is my interpretation, but I can be missing something. Hope it helps.
Upvotes: 2
Reputation: 22374
If you really need such side-effect in your code - you can see "hello" when accessing A directly (so just mention A in your trait). This is a feature of objects - they don't initialize while they don't really need to:
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
You may notice simmilar behavior for type members:
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>
Upvotes: 4
Reputation: 1182
When you initialize a in the trait all you did is copy a reference to function A.init into SomeTrait.a. You could then do a() to invoke the function and see the expected output.
Upvotes: 0