1. Overview
In this article, we’ll be looking at one of the most interesting features in the Kotlin syntax – which is the lazy keyword – used for creating lazy-initialized objects. We will be also looking at the
We’ll be also looking at the lateinit keyword that allows us to trick compiler and initialize non-null fields in the body of class – instead of in the constructor.
2. Lazy Initialization Pattern in Java
Sometimes we need to construct objects that have a very heavy initialization process. Also, often we cannot be sure that 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 major flaws in multi-threading, testing, etc – and they’re now widely known as anti-patterns to be avoided.
Alternatively, we can leverage the static initialization of 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 actually 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 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 a 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 keyword:
@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. Default publication mode is SYNCHRONIZED meaning that only 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 givenLazyValue_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 cause initialization of the ClassWithHeavyInitialization happen twice.
There’s also a third mode – NONE – but it shouldn’t be used in the multi-threaded environment as its behavior is undefined.
4. Kotlin’s lateinit
In Kotlin, every variable that is declared in the class needs to be assigned in the constructor; otherwise, we’ll get a compiler error. On the other hand, there are some cases in which the variable can be assigned dynamically by for example dependency injection.
To defer 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) }
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 a very cumbersome and needs a lot of boilerplate code.
Next, we delved into Kotlin lazy keyword that is used for lazy initialization of properties. At 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 in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.