Keep construction rules in one place
A primary constructor declares the main initialization path. init blocks run during construction. Secondary constructors must delegate to another constructor, which ensures primary initialization is shared.
class Session(val topic: String, val minutes: Int) {
init {
require(topic.isNotBlank())
require(minutes > 0)
}
constructor(topic: String) : this(topic, 25)
}
fun main() {
check(Session("Kotlin").minutes == 25)
}
Both constructors enforce the same invariants. Property initializers and init blocks run in declaration order. Avoid calling overridable functions during base-class initialization: the subclass's properties may not yet be ready when the override executes.
Use default parameters instead of secondary constructors when the only difference is an omitted argument. Named factory functions can express different creation policies more clearly than several constructors taking similar types.
Exercise
Replace the secondary constructor with a default minutes parameter. Add tests for blank topics and zero minutes through both normal and defaulted calls.
Check: no public creation path should construct a session that breaks its documented rules.