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.

Actual problem

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#Bar() calls parent class constructor Foo#Foo() by invokespecial #19 Foo a field is initialized with 1 . Foo#b is initialized with result of Foo#a() .

Foo ‘s constructor initializes a as expected – by assigning 1 to it ( iconst_1 and 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 ‘s during 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.

Summary