Vals gotcha! — VirtusLab
Immutability in Scala is great. A lot of concerns go away when you use it, but those that remain are not so clear to reason about.
Look at the code below:
My question is: what is the value of expression
(new Bar).b? In this post I will explain why it is neither 1 nor 2.
There is a general explanation of why similar problems spring up in Scala but this post contains more in-depth one.
In every book about Scala you will read something more or less like that: vals are implemented as private fields with public getters. But this is not the full truth about vals. Unfortunately, this public getter is the source of all evil.
Consider how it is that field is marked private and final and you can still inherit it from superclass? How is it made to be polymorphic? The thing that makes it possible is an ‘implicit’ polymorphic getter in class body for each declared val. When you override
vals inherited from parent class, compiler actually overrides these public getters with references to new private fields.
What is the actual value and why?
Look at another code snippet showing constructors translated to bytecode
Based on the example above look at the initialization process:
Bar#Bar()is called when creating new instance of
Bar#Bar()calls parent class constructor
afield is initialized with
Foo#bis initialized with result of
Foo‘s constructor initializes
a as expected – by assigning 1 to it (
putfield #14) ( reminder! JVM uses stack to manipulate data).
a initialization in
Bar class looks very similar. In
Foo#b initialization looks odd.
b is initialized by taking result of
Bar#a() called by
invokevirtual #24(invokevirtual). If there was no inheritance or overriding in this example this wouldn’t look suspicious at all. Using polymorphic methods in constructors is not a good idea. It can lead to wrong initialization like in the example above.
Foo#a() is redefined in
Bar class. It means that the one from
Bar will be used instead of
Foo initialization. But
Bar is not initialized at the point of call to
Bar#a(), thus its fields have still default values (in this case 0). And that answers the question what is the actual value. It is 0.
Could it be done better than that? Java does not provide polymorphic fields at all. Primitives are particularly good example here because default primitves values are inserted instead of
null. This leads to non obvious program behaviour at runtime without errors. If it had been a reference type, there would have been an immediate NullPointerException drawing attention to this piece of code.
Originally published at virtuslab.com.