# Late Initialization

Sometimes you want to initialize properties of your class after it is created, but in a separate member function instead of using lazy.

For example, a framework or library might require initialization in a special function. If you extend that library class, you can provide your own implementation of that special function.

Consider a Bag interface with a setUp() that initializes instances:

// LateInitialization/Bag.kt
package lateinitialization

interface Bag {
  fun setUp()
}

Suppose we want to reuse a library that creates and manipulates Bags and guarantees that setUp() is called. This library requires subclass initialization in setUp() instead of in a constructor:

// LateInitialization/Suitcase.kt
package lateinitialization
import atomictest.eq

class Suitcase : Bag {
  private var items: String? = null
  override fun setUp() {
    items = "socks, jacket, laptop"
  }
  fun checkSocks(): Boolean =
    items?.contains("socks") ?: false
}

fun main() {
  val suitcase = Suitcase()
  suitcase.setUp()
  suitcase.checkSocks() eq true
}

Suitcase initializes items by overriding setUp(). However, we can’t just define items as a String—if we do that, we must provide a non-null initializer in the constructor. Using a stub value such as an empty String is a bad practice because you never know whether it’s actually been initialized. null indicates that it’s not initialized.

Defining items as a nullable String? means we must check for null in all member functions, as in checkSocks(). However, we know that the library we’re reusing initializes items by calling setUp(), so the null checks should not be necessary.

The lateinit property modifier fixes this problem—here, we initialize items after creating an instance of BetterSuitcase:

// LateInitialization/BetterSuitcase.kt
package lateinitialization
import atomictest.eq

class BetterSuitcase : Bag {
  lateinit var items: String
  override fun setUp() {
    items = "socks, jacket, laptop"
  }
  fun checkSocks() = "socks" in items
}

fun main() {
  val suitcase = BetterSuitcase()
  suitcase.setUp()
  suitcase.checkSocks() eq true
}

Compare this version of checkSocks() with the one in Suitcase.kt. lateinit means items is safely defined as a non-nullable property.

lateinit can be used on a property inside the body of a class, a top-level property, or local var.

Limitations:

  • lateinit can only be used on a var property, not a val.
  • The property must be a non-nullable type.
  • The property cannot be a primitive type.
  • lateinit is not allowed for abstract properties in an abstract class or interface.
  • lateinit is not allowed for properties with a custom get() or set().

What happens if you forget to initialize such a property? You won’t get compile-time errors or warnings, because the initialization logic might be complex and depend on other properties that Kotlin can’t monitor:

// LateInitialization/FaultySuitcase.kt
package lateinitialization
import atomictest.*

class FaultySuitcase : Bag {
  lateinit var items: String
  override fun setUp() {}
  fun checkSocks() = "socks" in items
}

fun main() {
  val suitcase = FaultySuitcase()
  suitcase.setUp()
  capture {
    suitcase.checkSocks()
  } eq
    "UninitializedPropertyAccessException" +
    ": lateinit property items " +
    "has not been initialized"
}

This runtime exception has enough detail for you to easily discover and fix the problem. Tracking down an error reported by a null pointer exception is usually much more difficult.

.isInitialized will tell you whether a lateinit property been initialized. The property must be in your current scope, and is accessed using the :: operator:

// LateInitialization/IsInitialized.kt
package lateinitialization
import atomictest.*

class WithLate {
  lateinit var x: String
  fun status() = "${::x.isInitialized}"
}

lateinit var y: String

fun main() {
  trace("${::y.isInitialized}")
  y = "Ready"
  trace("${::y.isInitialized}")
  val withlate = WithLate()
  trace(withlate.status())
  withlate.x = "Set"
  trace(withlate.status())
  trace eq "false true false true"
}

Although you can create a local lateinit var, you cannot call .isInitialized on it because references to local vars or vals are not supported.

Exercises and solutions can be found at www.AtomicKotlin.com.