1. Overview

In this article, we’ll be looking at one of the most interesting features in Kotlin syntax – lazy initialization.

We’ll also be looking at the lateinit keyword that allows us to trick the compiler and initialize non-null fields in the body of the class – instead of in the constructor.

2. Lazy Initialization Pattern in Java

Sometimes we need to construct objects that have a cumbersome initialization process. Also, often we cannot be sure that the object, for which we paid the cost of initialization at the start of our program, will be used in our program at all.

The concept of ‘lazy initialization’ was designed to prevent unnecessary initialization of objects. In Java, creating an object in a lazy and thread-safe way is not an easy thing to do. Patterns like Singleton have significant flaws in multithreading, testing, etc. – and they’re now widely known as anti-patterns to be avoided.

Alternatively, we can leverage the static initialization of the inner object in Java to achieve laziness:

public class ClassWithHeavyInitialization {
 
    private ClassWithHeavyInitialization() {
    }

    private static class LazyHolder {
        public static final ClassWithHeavyInitialization INSTANCE = new ClassWithHeavyInitialization();
    }

    public static ClassWithHeavyInitialization getInstance() {
        return LazyHolder.INSTANCE;
    }
}

Notice how, only when we will call the getInstance() method on ClassWithHeavyInitialization, the static LazyHolder class will be loaded, and the new instance of the ClassWithHeavyInitialization will be created. Next, the instance will be assigned to the static final INSTANCE reference.

We can test that the getInstance() is returning the same instance every time it is called:

@Test
public void giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall() {
    // when
    ClassWithHeavyInitialization classWithHeavyInitialization 
      = ClassWithHeavyInitialization.getInstance();
    ClassWithHeavyInitialization classWithHeavyInitialization2 
      = ClassWithHeavyInitialization.getInstance();

    // then
    assertTrue(classWithHeavyInitialization == classWithHeavyInitialization2);
}

That’s technically OK but of course a little bit too complicated for such a simple concept.

3. Lazy Initialization in Kotlin

We can see that using the lazy initialization pattern in Java is quite cumbersome. We need to write a lot of boilerplate code to achieve our goal. Luckily, the Kotlin language has built-in support for lazy initialization.

To create an object that will be initialized at the first access to it, we can use the lazy method:

@Test
fun givenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce() {
    // given
    val numberOfInitializations: AtomicInteger = AtomicInteger()
    val lazyValue: ClassWithHeavyInitialization by lazy {
        numberOfInitializations.incrementAndGet()
        ClassWithHeavyInitialization()
    }
    // when
    println(lazyValue)
    println(lazyValue)

    // then
    assertEquals(numberOfInitializations.get(), 1)
}

As we can see, the lambda passed to the lazy function was executed only once.

When we’re accessing the lazyValue for the first time – an actual initialization happened, and the returned instance of the ClassWithHeavyInitialization class was assigned to the lazyValue reference. Subsequent access to the lazyValue returned the previously initialized object.

We can pass the LazyThreadSafetyMode as an argument to the lazy function. The default publication mode is SYNCHRONIZED, meaning that only a single thread can initialize the given object.

We can pass a PUBLICATION as a mode – which will cause that every thread can initialize given property. The object assigned to the reference will be the first returned value – so the first thread wins.

Let’s have a look at that scenario:

@Test
fun whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce() {
 
    // given
    val numberOfInitializations: AtomicInteger = AtomicInteger()
    val lazyValue: ClassWithHeavyInitialization
      by lazy(LazyThreadSafetyMode.PUBLICATION) {
        numberOfInitializations.incrementAndGet()
        ClassWithHeavyInitialization()
    }
    val executorService = Executors.newFixedThreadPool(2)
    val countDownLatch = CountDownLatch(1)
 
    // when
    executorService.submit { countDownLatch.await(); println(lazyValue) }
    executorService.submit { countDownLatch.await(); println(lazyValue) }
    countDownLatch.countDown()

    // then
    executorService.awaitTermination(1, TimeUnit.SECONDS)
    executorService.shutdown()
    assertEquals(numberOfInitializations.get(), 2)
}

We can see that starting two threads at the same time causes the initialization of the ClassWithHeavyInitialization to happen twice.

There’s also a third mode – NONE – but it shouldn’t be used in the multithreaded environment as its behavior is undefined.

4. Kotlin’s lateinit

In Kotlin, every non-nullable class property that is declared in the class should be initialized either in the constructor or as part of the variable declaration. If we fail to do that, then the Kotlin compiler will complain about an error message:

Kotlin: Property must be initialized or be abstract

This basically means that we should either initialize the variable or mark it as abstract.

On the other hand, there are some cases in which the variable can be assigned dynamically by for example dependency injection.

To defer the initialization of the variable, we can specify that a field is lateinit. We are informing the compiler that this will variable will be assigned later and we are freeing the compiler from the responsibility of making sure that this variable gets initialized:

lateinit var a: String
 
@Test
fun givenLateInitProperty_whenAccessItAfterInit_thenPass() {
    // when
    a = "it"
    println(a)

    // then not throw
}

If we forget to initialize the lateinit property, we’ll get an UninitializedPropertyAccessException:

@Test(expected = UninitializedPropertyAccessException::class)
fun givenLateInitProperty_whenAccessItWithoutInit_thenThrow() {
    // when
    println(a)
}

It’s worth mentioning that we can only use lateinit variables with non-primitive data types. Therefore, it’s not possible to write something like this:

lateinit var value: Int

And if we do so, we would get a compilation error:

Kotlin: 'lateinit' modifier is not allowed on properties of primitive types

5. Conclusion

In this quick tutorial, we looked at the lazy initialization of objects.

Firstly, we saw how to create a thread-safe lazy initialization in Java. We saw that it is very cumbersome and needs a lot of boilerplate code.

Next, we delved into the Kotlin lazy keyword that is used for lazy initialization of properties. In the end, we saw how to defer assigning variables using the lateinit keyword.

The implementation of all these examples and code snippets can be found over on GitHub.


« 上一篇: Kotlin与Mockito
» 下一篇: Kotlin集合API概述