Kotlin properties – from basic to lazy

Kotlin has many things going for it, with one of the main qualities being its built-in null-safety. This guarantees that properties will always have a non-null value at runtime, greatly reducing the risks of coding errors and NullpointerExceptions. In languages where this isn’t the case — Java, anyone? — the lack of this feature is sometimes (ab)used by the libraries and frameworks we utilize (Spring and its property injection for instance).

This post will cover the way Kotlin deals with properties and their initialization.

Immediate initialization

Consider the following code, which defines a Environment class, with a single property named applicationName.

See the listing below for the equivalent Java code. You can try this yourself if you are an Intellij user. Under the tools -> Kotlin menu option, there’s a show kotlin bytecode option. On the view that opens, a ‘decompile’ button is visible, which can be pressed to get the Java code that would result in the given bytecode:

We see a final field which is declared to be private final with a value assigned to it directly, and a getApplicationName() method that allows us to access this property from outside the class.

Running the application with the main() function supplied, will output the following:

Application Name

Process finished with exit code 0

Nothing really suprising here; when we create an instance of the class at runtime it will automatically have the applicationName property populated with the specified value. In Kotlin this value will never change, due to val being immutable. We have created a constant value that’s neither null nor any other value than applicationName for the full lifetime of the application.

Alternatively we can define a property and provide a getter for it:

This will work the same from a Kotlin perspective. However, the generated bytecode (and decompiled Java code) show a difference:

There’s no field! Interestingly, the kotlin code is compiled into a single getter that returns the value specified in the declaration directly. Of course, you won’t see the difference when accessing the property, but the resulting code surely is different, more concise and doesn’t require accessing a property that won’t change anyway.

Init Blocks

What if we wanted to initialize the property and print a statement that we did this in the console. We can’t do that by initializing it this way. We could use an init{} block for this purpose:

We can now declare the property without immediately assigning a value. We still have to assign it a value though (or we’ll be faced with compilation errors), since the property is declared as non-null. We can do this using init{} block.

This is the Java equivalent for the given example:

We see that the resulting code is similar to the declaration of the property without defining an explicit get(). The value is not initialized inline though, but in a generated constructor that contains the body of the init block. This is the kotlin construct that allows you to execute code such as println during construction. Primary constructors can’t have a body in kotlin, something you can get around by using the init{} block.

Injection at runtime

What if you have an application that defines or populates your properties at runtime? If a framework such as Spring injects a value, you will not know IF it will be available and more importantly WHEN it will be set for you. The kotlin compiler will stop you from specifying non-null properties without an initial value, but you might still want to declare them as such. We can get around this by using the lateinit keyword:

The compiler will no longer complain over the property being declared as non-null without assigning a value to it. It does build in a safeguard, which will become apparent when comparing the equivalent Java code:

Interesting stuff is happening here. The field isn’t declared as we would expect, not as private final but as public. The reason for this is that java frameworks doing dependency injection (or frameworks accessing properties later than Kotlin prefers) sometimes require properties to be public in order to set them. This adds another caveat for this code, if you were to call it from Java the property will be exposed and can be changed or set to null. If you do not use the generated getter or setter this could break the behaviour.

There are two additional constructs, a getter and a setter being created, and this is where the non-null nature of the property is being enforced. If we were to access the getter without the property being initialized, this will result in an UninitializedPropertyAccessException being thrown. So, if you are expecting a framework to populate the property but it doesn’t happen, kotlin will let you know as soon as you access the property. This is at runtime though, which is not ideal (and not too different from the well-known NPE). Another drawback is that the setter can be called by anyone in the application – including you – which means some code may be initializing the property without you really wanting it. Also, providing a setter allows users to modify the value of the property after it was initialized by another process, which is generally unfavorable.

Please note; even though lateinit is built into the language, generally speaking you would favor constructor or setter injection over property injection when using these frameworks. Constructor injection in particular allows you to still use non-null immutable values for your properties, where lateinit does not.

Delegated properties

A last method of initialization we’ll cover here is the use of delegated properties. Instead of declaring a predefined value for a property, we are going to leave initialization to a delegate. Consider the following code:

The first line covers the property declaration. We use the keyword by to specify that the actual value for the property is coming from the delegate class called FileLoaderDelegate. We are creating an instance here taking a filename as a parameter.

Next up is the class we are going to use as a delegate. Kotlin requires a delegate class to declare the getValue operator function. This function takes two parameters; thisRef being the instance the property is defined on, and property being the property that is delegated for (using reflection). If the property being delegated would have been mutable – a var – kotlin would require you to define a setValue function too, with an additional parameter which is the value being set on the var.

Finally, we are accessing the property in the main function.

One of the main things to be aware of when using a delegate is that it will be evaluated every time the property is accessed. In this example, we (for some reason) want to load the contents of a file into a property. This only happens when the property is accessed, so if we do not access the property in our code at runtime, we won’t read from any files. We do need to make sure we are not loading the file repeatedly, as readFile will be called every time we access the property, which is not efficient.

So, we’d be better off with some form of lazy initialization, a property that is initialized once and then cached for every subsequent usage. The kotlin standard library comes to the rescue, because there’s just that and it is even called lazy too – how intuitive!

Using this, will turn the code above in the following:

The lazy delegate receives a lambda which describes that the val should be populated by reading the val. This lambda is evaluated once and once only; when accessing the property for the first time. It is then cached and returned for each subsequent time the property is accessed.

By now you may wonder, is all this magic threadsafe? Indeed it is! By default, lazy initializes the value while using a lock to prevent concurrent access. This way we can prevent the value being initialized more than once. We can define an explicitly define the mode parameter. There are three:

  • LazyThreadSafetyMode.SYNCHRONIZED which is used by default and uses a lock when initializing;
  • LazyThreadSafetyMode.PUBLICATION which will allow the property initializer to be called multiple time concurrently during initialization, but only the first resolved value that is returned will be used to initialize the property;
  • LazyThreadSafetyMode.NONE this is the non-threadsafe approach which will allow concurrent access to the property during initialization and access.

For all three, accessing the property once it is resolved can be done concurrently, so only the first access can result in having to wait for a lock.

I hope this article gives some insights in how to deal with immutable, non-null properties in kotlin and how to initialize them (especially when you have to do this at runtime). Whether you directly initialize them, use lateinit or delegated properties, kotlin provides the tools to work with your properties in most (if not all) scenarios.

The default delegates in the kotlin standard library already tackle some of the common cases, but as you saw in the code examples, being to define a type of delegate yourself can be very convenient. Always a good tool to have available in your toolbox.

Got questions, comments? Let me know in the comments below!

Did you enjoy this content? We have got more on the way! Sign up here for regular updates!

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *