How Kotlin’s delegated properties and lazy-initialization work

Chang W. Doh
TIL: Kotlin in Practice
8 min readSep 10, 2017

Accessing properties is very familiar in programming languages ​​that support object-oriented paradigms. Kotlin also provides a number of such approaches, and lazy initialization with by lazy is a good example of them.

In this article, we will look at how to use Kotlin's delegation to handle properties and by lazy for lazy initialization then dive into how they work.

Nullable type

I thought many of you may already know nullable, but let’s take a look at it again. The Android component code using Kotlin could probably be written as:

class MainActivity : AppCompatActivity() {
private var helloMessage : String = "Hello"
}

Initialization in own lifecycle and Nullable type

In the above example, there is no big problem if initialization is possible with object creation. However, you can not declare and use a value in advance if you refer to it after a specific initialization process because it has its own lifecycle to initialize itself.

Let’s take a look at some familiar Java code.

public class MainActivity extends AppCompatActivity {
private TextView mWelcomeTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWelcomeTextView = (TextView) findViewById(R.id.msgView);
}
}

You can write Kotlin code like above by declaring it as a nullable type as you can see below.

class MainActivity : AppCompatActivity() {
private var mWelcomeTextView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mWelcomeTextView = findViewById(R.id.msgView) as TextView
}
}

Non-null type

Above code works well, but it is a bit tedious to check each time whether it is null before using properties in your code. You can omit this by using a non-null type that (you believe) it always has a value.

class MainActivity: AppCompatActivity () { 
private var mWelcomeTextView: TextView
...
}

Of course, you need to use lateinit to tell you'll assign a value to the widget later.

lateinit: I’ll initialize non-null properties lately

Unlike lazy initialization, which we generally talk about, lateinit allows the compiler to recognize that the value of the non-null property is not stored in the constructor stage to compile normally.

class MainActivity : AppCompatActivity() {
private lateinit var mWelcomeTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mWelcomeTextView = findViewById(R.id.msgView) as TextView
}
}

More information here .

Read-only property

In general, if the field of a component is not a primitive type or a built-in type, you can see that references are kept on the whole lifecycle of the component.

For example, in an Android application, most widget references keep the same in the lifecycle of the activity. In other wise, this means that you rarely need to change the reference you assigned.

At this point, we can easily come up with the following ideas:

“If the value of property is usually kept in the component’s lifetime, is the read-only type enough for keeping the value?”

I think so. To do this, at first glance, we need only a little effort to replace var with val.

Dilemma for Non-null with read-only properties

However when we declare the read-only property, we are confronted with the problem that we can not define where to perform initialization.

class MainActivity : AppCompatActivity() {
private val mWelcomeTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Where do I move the initialization code?????
// mWelcomeTextView = findViewById(R.id.msgView) as TextView

}
}

Now let’s try to solve the last problem:

“How do we apply a read-only property that should be assigned lately”

lazy Initialization

by lazy may be very useful when implementing read-only properties that perform lazy-initialization in Kotlin.

by lazy { ... } performs its initializer where the defined property is first used, not its declaration.

class MainActivity : AppCompatActivity() {
private val messageView : TextView by lazy {
// runs on first access of messageView
findViewById(R.id.message_view) as TextView
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun onSayHello() {
// Initialization would be run at here!!
messageView.text = "Hello"
}
}

Now, we can declare a read-only property without worrying about the initialization point of messageView. Let's see how by lazy works.

Delegated property 101

Delegation literally means delegation. It means the delegator can perform some operation instead the original accessor.

Delegated property delegates a getter/setter of the property, which allows the delegated object to perform some intermediate action when reading and writing the value.

Kotlin supports delegating the implementation of an interface(Class Delegation) or accessors(Delegated Properties) to another object. More details will be covered in another article. :)

Delegation is a something with a historic background. :) (Source: Wikipedia commons )

You can declare a delegated property in the following by <delegate> format :

val / var <property name>: <Type> by <delegate>

The delegation of a property can be defined as follows:

class Delegate {
operator fun getValue(
thisRef: Any?,
property: KProperty<*>
): String {
// return value
}
operator fun setValue(
thisRef: Any?,
property: KProperty<*>, value: String
) {
// assign
}
}

All read operations on the value are changed to a call to getValue() on the delegated target, likewise setValue() will be called in case of a write operation.

How ‘by lazy’ works

Now let’s revisit the code for that property again.

It’s just a delegted property!

We can understand by lazy make a property as a delegated property with lazy delegation.

So, how does lazy work? Let’s summarize lazy() in the Kotlin standard library reference as follows:

  1. lazy() returns a Lazy<T> instance that stored lambda initializer.
  2. The first call of getter executes a lambda passed to lazy() and stores its result.
  3. Subsequently, the getter execution returns the stored value.

Simply, lazy creates an instance that performs initialization at the first access to the property value, stores the result, and returns the stored value.

Delegated property with lazy()

Let's write a simple Kotlin code to check the implementation of lazy.

class Demo { 
val myName: String by lazy { "John" }
}

If you do decompiling it to Java code, you can see the following code:

public final class Demo {
@NotNull
private final Lazy myName$delegate;

// $FF: synthetic field
static final KProperty[] $$delegatedProperties = ...
@NotNull
public final String getMyName() {
Lazy var1 = this.myName$delegate;
KProperty var3 = $$delegatedProperties[0];
return (String)var1.getValue();
}
public Demo() {
this.myName$delegate =
LazyKt.lazy((Function0)null.INSTANCE);
}
}
  • Postfix $delegateappends to the field name: myName$delegate
  • Note that the type of myName$delegate is Lazy, not String.
  • In the constructor, LazyKt.lazy() is assigned to myName$delegate.
  • LazyKt.lazy() is responsible for executing the given initialization block.

The actual action is that invoking to getMyName() will return the value of Lazy instance by getValue() of myName$delegate.

Lazy implementations

lazy returns an Lazy<T> object that handles the lambda function(initializer) performing the initialization in a slightly different way according to the thread execution mode(LazyThreadSafetyMode).

@kotlin.jvm.JvmVersion
public fun <T> lazy(
mode: LazyThreadSafetyMode,
initializer: () -> T
): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED ->
SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION ->
SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE ->
UnsafeLazyImpl(initializer)
}

All of them are responsible for calling the given lambda block for lazy-initialization.

SYNCHRONIZED → SynchronizedLazyImpl

  • Initialization is executed only on the first thread that is called first.
  • Other threads will then refer to the value cached.
  • Default mode(LazyThreadSafetyMode.SYNCHRONIZED)

PUBLICATION → SafePublicationLazyImpl

  • It can be called from multiple threads at the same time, and initialization can be done simultaneously on all or part of the threads.
  • However, if a value that has already been initialized by another thread, it will be returned without performing a initialization.

NONE → UnsafeLazyImpl

  • Simply initializes it at the first access, or returns the stored value.
  • No consideration for multi-threads, so it’s unsafe.

The default behavior of Lazy implementations

SynchronizedLazyImpl and SafePublicationLazyImpl, UnsafeLazyImpl are do performing lazy-initialization via the following process. Let’s check with earlier example.

Easy code sample is always a good thing! :)

1. Store the passed initialization lambda in a property initializer.

The initialization lambda is not executed, but is stored for later execution.

2. Store the value through the property _value. The initial value of this property is UNINITIALIZED_VALUE.

UNINITIALIZED_VALUE is assigned to represent that the value is not initialized.

3. if _value is UNINITIALIZED_VALUE at the read access, executes the initializer.

lazy-Initialization is performed by executing initializer at first access.

4. If _value is NOT UNINITIALIZED_VALUE, the read access will return _value since the initialization has already been done.

Now that the stored value exists, the call to getValue () will return “John”.

SynchronizedLazyImpl

If you do not specify a particular mode, lazy implementations would be SynchronizedLazyImpl which performs the initialization only once by default . Let's look at its implementation code .

private object UNINITIALIZED_VALUEprivate class SynchronizedLazyImpl<out T>(
initializer: () -> T,
lock: Any? = null
) : Lazy<T>,
Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required
// to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
}
else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean =
_value !== UNINITIALIZED_VALUE
override fun toString(): String =
if (isInitialized()) value.toString()
else "Lazy value not initialized yet."
private fun writeReplace(): Any =
InitializedLazyImpl(value)
}

It seems to be a bit complicated. but it’s just the same implementation for multi-threading.

  • Executing the initialization block with synchronized().
  • Since the initialization may already be finished by another thread, it does double-check. If initialization already has been done, it returns the stored value.
  • If it’s uninitialized, it will execute the lambda expression and store the return value. Theninitializer will be null, because it’s no longer needed upon initialization completion .

Kotlin’s delegated properties make you 😀

Of course, lazy-initialization can sometimes cause a problem to occur or make debugging difficult by bypassing the control-flow and generating a normal value under abnormal situations.

However, if you are careful about these cases, the lazy-initialization from Kotlin can be make us more free from concerns about thread-safe, performance.

We have also confirmed that lazy-initialization is a result by operator by and a function lazy. There are many more delegations such as Observable and notNull. And you can also implement interesting delegated properties if necessary. Enjoy!

This is a translation of my article. If you find wrong part, typo error or any strange part. Feel free to address them. I always welcome your comment or email! :)

--

--

Chang W. Doh
TIL: Kotlin in Practice

I’m doing something looks like development. Community diver. ex-Google Developer Expert for Web 😎